source('../env.R')

Species in communities

It seems reasonable to expect that cities with simialr regional pools will have similar species entering the city, and thus a similar response to urbanisation.

Load data

city_effort = read_csv(filename(CITY_DATA_OUTPUT_DIR, 'city_effort.csv'))
Rows: 342 Columns: 7── Column specification ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr (1): city_name
dbl (6): city_id, total_city_checklists, total_city_locations, total_city_effort, total_city_area_m2, percentage_total_city_area_surveyed
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
city_effort
communities = read_csv(filename(COMMUNITY_OUTPUT_DIR, 'communities_for_analysis.csv'))
Rows: 2462 Columns: 12── Column specification ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr (5): city_name, jetz_species_name, seasonal, presence, origin
dbl (4): city_id, distance_to_northern_edge_km, distance_to_southern_edge_km, relative_abundance_proxy
lgl (3): present_urban_high, present_urban_med, present_urban_low
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
communities_summary = communities %>% group_by(city_id) %>% summarise(
  regional_pool_size = n(), 
  urban_pool_size = sum(relative_abundance_proxy > 0)
) %>% left_join(city_effort %>% dplyr::select(city_id, percentage_total_city_area_surveyed))
Joining with `by = join_by(city_id)`
ggplot(communities %>% filter(relative_abundance_proxy > 0), aes(x = relative_abundance_proxy)) + geom_bar(stat = "bin")

city_points = st_centroid(read_sf(filename(CITY_DATA_OUTPUT_DIR, 'city_selection.shp')))
Warning: st_centroid assumes attributes are constant over geometriesWarning: st_centroid does not give correct centroids for longitude/latitude data
community_data_metrics = read_csv(filename(COMMUNITY_OUTPUT_DIR, 'community_assembly_metrics_using_relative_abundance.csv')) %>%
  dplyr::select(city_id, mntd_normalised, fdiv_normalised, mass_fdiv_normalised, handwing_index_fdiv_normalised, trophic_trait_fdiv_normalised, gape_width_fdiv_normalised) %>%
  left_join(read_csv(filename(CITY_DATA_OUTPUT_DIR, 'realms.csv'))) %>%
  left_join(communities_summary) %>%
  left_join(city_points[,c('city_id', 'city_nm')]) %>%
  rename(city_name='city_nm') %>%
  na.omit() %>%
  arrange(city_id)
Rows: 341 Columns: 43── Column specification ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
dbl (43): mntd_normalised, mntd_actual, mntd_min, mntd_max, mntd_mean, mntd_sd, fdiv_normalised, fdiv_actual, fdiv_min, fdiv_max, fdiv_mean, fdiv_sd, mass_fdi...
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Rows: 342 Columns: 2── Column specification ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr (1): core_realm
dbl (1): city_id
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Joining with `by = join_by(city_id)`Joining with `by = join_by(city_id)`Joining with `by = join_by(city_id)`
community_data_metrics
community_data_metrics %>% group_by(core_realm) %>% summarise(total_cities = n())
test_value = function(name, normalised_list) {
  wilcox_test_result = wilcox.test(normalised_list, mu = 0.5)
  
  significance = ifelse(wilcox_test_result$p.value < 0.0001, '***', 
                        ifelse(wilcox_test_result$p.value < 0.001, '**', 
                               ifelse(wilcox_test_result$p.value < 0.01, '*', '')))
  m = mean(normalised_list)
  
  paste(name, 'mean', round(m, 2), significance)
}
test_value('Global MNTD', community_data_metrics$mntd_normalised)
[1] "Global MNTD mean 0.5 "
test_value('Global beak gape FDiv', community_data_metrics$gape_width_fdiv_normalised)
[1] "Global beak gape FDiv mean 0.59 ***"
test_value('Global HWI FDiv', community_data_metrics$handwing_index_fdiv_normalised)
[1] "Global HWI FDiv mean 0.61 ***"
nrow(community_data_metrics)
[1] 319

Load trait data

traits = read_csv(filename(TAXONOMY_OUTPUT_DIR, 'traits_jetz.csv'))
Rows: 304 Columns: 6── Column specification ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr (1): jetz_species_name
dbl (5): gape_width, trophic_trait, locomotory_trait, mass, handwing_index
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
head(traits)
fetch_normalised_traits = function(required_species_list) {
  required_traits = traits %>% filter(jetz_species_name %in% required_species_list)
  
  required_traits$gape_width_normalised = normalise(required_traits$gape_width, min(required_traits$gape_width), max(required_traits$gape_width))
  required_traits$handwing_index_fdiv_normalised = normalise(required_traits$handwing_index, min(required_traits$handwing_index), max(required_traits$handwing_index))
  required_traits$mass_normalised = normalise(required_traits$mass, min(required_traits$mass), max(required_traits$mass))
  
  traits_normalised_long = required_traits %>% pivot_longer(cols = c('gape_width_normalised', 'handwing_index_fdiv_normalised', 'mass_normalised'), names_to = 'trait', values_to = 'normalised_value') %>% dplyr::select(jetz_species_name, trait, normalised_value)
  traits_normalised_long$trait = factor(traits_normalised_long$trait, levels = c('gape_width_normalised', 'handwing_index_fdiv_normalised', 'mass_normalised'), labels = c('Gape Width', 'HWI', 'Mass'))
  
  traits_normalised_long
}

fetch_normalised_traits(c('Aplopelia_larvata', 'Chalcophaps_indica', 'Caloenas_nicobarica'))

Read in our phylogeny

phylo_tree = read.tree(filename(TAXONOMY_OUTPUT_DIR, 'phylogeny.tre'))
ggtree(phylo_tree, layout='circular')

Load resolve ecoregions

resolve = read_resolve()
Warning: st_buffer does not correctly buffer longitude/latitude datadist is assumed to be in decimal degrees (arc_degrees).
Warning: st_simplify does not correctly simplify longitude/latitude data, dTolerance needs to be in decimal degrees

Create helper functions

to_species_matrix = function(filtered_communities) {
  filtered_communities %>% 
    dplyr::select(city_id, jetz_species_name) %>% 
    distinct() %>%
    mutate(present = TRUE) %>% 
    pivot_wider(
      names_from = jetz_species_name, 
      values_from = "present", 
      values_fill = list(present = F)
    ) %>% 
    tibble::column_to_rownames(var='city_id')
}
community_nmds = function(filtered_communities) {
  species_matrix = to_species_matrix(filtered_communities)
  nmds <- metaMDS(species_matrix, k=2, trymax = 30)
  nmds_result = data.frame(vegan::scores(nmds)$sites)
  nmds_result$city_id = as.double(rownames(nmds_result))
  rownames(nmds_result) = NULL
  nmds_result
}

https://www.datacamp.com/tutorial/k-means-clustering-r

scree_plot = function(community_nmds_data) {
  # Decide how many clusters to look at
  n_clusters <- min(10, nrow(community_nmds_data) - 1)
  
  # Initialize total within sum of squares error: wss
  wss <- numeric(n_clusters)
  
  set.seed(123)
  
  # Look over 1 to n possible clusters
  for (i in 1:n_clusters) {
    # Fit the model: km.out
    km.out <- kmeans(community_nmds_data[,c('NMDS1','NMDS2')], centers = i, nstart = 20)
    # Save the within cluster sum of squares
    wss[i] <- km.out$tot.withinss
  }
  
  # Produce a scree plot
  wss_df <- tibble(clusters = 1:n_clusters, wss = wss)
   
  scree_plot <- ggplot(wss_df, aes(x = clusters, y = wss, group = 1)) +
      geom_point(size = 4) +
      geom_line() +
      geom_hline(linetype="dashed", color = "orange", yintercept = wss) +
      scale_x_continuous(breaks = c(2, 4, 6, 8, 10)) +
      xlab('Number of clusters')
  scree_plot
}
cluster_cities = function(city_nmds, cities_community_data, centers) {
  set.seed(123)
  kmeans_clusters <- kmeans(city_nmds[,c('NMDS1', 'NMDS2')], centers = centers, nstart = 20)
  city_nmds$cluster = kmeans_clusters$cluster
  cities_community_data %>% left_join(city_nmds) %>% mutate(cluster = as.factor(cluster))
}
plot_nmds_clusters = function(cluster_cities) {
  cluster_cities %>% dplyr::select(city_id, city_name, NMDS1, NMDS2, cluster) %>% distinct() %>%
  ggplot(aes(x = NMDS1, y = NMDS2, colour = cluster)) + geom_point() + geom_label_repel(aes(label = city_name))
}
get_presence_cell_width = function(city_cluster_data_metrics) {
  10 * length(unique(city_cluster_data_metrics$city_id))
}

get_presence_cell_height = function(city_cluster_data_metrics) {
  species = species_in_cluster = communities %>% 
    filter(city_id %in% city_cluster_data_metrics$city_id) %>% 
    dplyr::select(jetz_species_name) %>% 
    distinct()
  
  10 * nrow(species)
}

city_metric_height = 30
traits_width = 50
phylo_tree_width = 125
title_height = 8

get_image_height = function(city_cluster_data_metrics) {
  get_presence_cell_height(city_cluster_data_metrics) + (3 * city_metric_height) + title_height
}

get_image_width = function(city_cluster_data_metrics) {
  get_presence_cell_width(city_cluster_data_metrics) + traits_width + phylo_tree_width
}
species_in_city_label = function(species) {
  paste(
    ifelse(species$seasonal == 'Resident', '', substring(species$seasonal, 0, 1)),
    ifelse(species$origin == 'Native', '', substring(species$origin, 0, 1)),
    ifelse(species$distance_to_northern_edge_km > 200, '', paste('NRL', round(species$distance_to_northern_edge_km), sep = ' ')),
    ifelse(species$distance_to_southern_edge_km > 200, '', paste('SRL', round(species$distance_to_northern_edge_km), sep = ' ')),
    sep = '\n'
  )
}

species_in_city_label(head(communities))
[1] "\nI\n\n" "\nI\n\n" "\nI\n\n" "P\n\n\n" "\n\n\n"  "\nI\n\n"
plot_city_cluster = function(city_cluster_data_metrics, title) {
  species_in_cluster = communities %>% 
    filter(city_id %in% city_cluster_data_metrics$city_id) %>% 
    dplyr::select(jetz_species_name, city_name, relative_abundance_proxy, seasonal, origin, distance_to_northern_edge_km, distance_to_southern_edge_km)
  
  species_in_cluster$label = species_in_city_label(species_in_cluster)
  tree_cropped <- ladderize(drop.tip(phylo_tree, setdiff(phylo_tree$tip.label, species_in_cluster$jetz_species_name)))
    
  gg_tree = ggtree(tree_cropped)
  
  gg_presence = ggplot(species_in_cluster, aes(x=city_name, y=jetz_species_name)) + 
          geom_tile(aes(fill=relative_abundance_proxy)) + 
          geom_text(aes(label=label), size=0.75) +
          scale_fill_gradientn(colours=c("#98FB98", "#FFFFE0", "yellow", "orange", "#FF4500", "red", "red"), values=c(0, 0.00000000001, 0.1, 0.25, 0.5, 0.75, 1), na.value = "transparent") +
          theme_minimal() + xlab(NULL) + ylab(NULL) + 
          theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1)) + 
          labs(fill='Urban Proxy Abundance')
  
  species_in_cluster_traits = fetch_normalised_traits(species_in_cluster$jetz_species_name)
  
  gg_traits = ggplot(species_in_cluster_traits, aes(x = trait, y = jetz_species_name, size = normalised_value)) + geom_point() + theme_minimal() + theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1), axis.text.y=element_blank()) + xlab(NULL) + ylab(NULL) + labs(size = "Normalised Value")
  
  gg_cities_mntd = ggplot(city_cluster_data_metrics, aes(x = city_name, y = mntd_normalised)) + geom_bar(stat = "identity") + theme_minimal() + theme(legend.position = "none", axis.text.x=element_blank()) + xlab(NULL) + ylab("MNTD") + ylim(0, 1)
  
  gg_cities_gape_fd = ggplot(city_cluster_data_metrics, aes(x = city_name, y = gape_width_fdiv_normalised)) + geom_bar(stat = "identity") + theme_minimal() + theme(legend.position = "none", axis.text.x=element_blank()) + xlab(NULL) + ylab("Gape") + ylim(0, 1)
  
  gg_cities_loco_fd = ggplot(city_cluster_data_metrics, aes(x = city_name, y = handwing_index_fdiv_normalised)) + geom_bar(stat = "identity") + theme_minimal() + theme(legend.position = "none", axis.text.x=element_blank()) + xlab(NULL) + ylab("HWI") + ylim(0, 1)
  
  gg_title = ggplot() + labs(title = title, subtitle = paste(
    test_value('MNTD', city_cluster_data_metrics$mntd_normalised),
    test_value('Compound FDiv', city_cluster_data_metrics$fdiv_normalised),
    test_value('HWI FDiv', city_cluster_data_metrics$handwing_index_fdiv_normalised),
    test_value('Gape width FDiv', city_cluster_data_metrics$gape_width_fdiv_normalised),
    sep = '\n'
  )) + theme_minimal() + theme(plot.subtitle=element_text(size=8, hjust=0, color="#444444"))
  
  gg_presence_height = get_presence_cell_height(city_cluster_data_metrics)
  gg_presence_width = get_presence_cell_width(city_cluster_data_metrics)
  
  gg_presence %>% insert_top(gg_cities_loco_fd, height = (city_metric_height / gg_presence_height)) %>% insert_top(gg_cities_gape_fd, height = (city_metric_height / gg_presence_height)) %>% insert_top(gg_cities_mntd, height = (city_metric_height / gg_presence_height)) %>% insert_left(gg_tree, width = (phylo_tree_width / gg_presence_width)) %>% insert_right(gg_traits, width = (traits_width / gg_presence_width)) %>% insert_top(gg_title, height = (title_height / gg_presence_height))
}
REGION_DEEP_DIVE_FIGURES_OUTPUT = mkdir(FIGURES_OUTPUT_DIR, 'appendix_regional_deep_dive_using_abundance')

Nearctic

nearctic_cities_community_data = community_data_metrics %>% filter(core_realm == 'Nearctic')
nearctic_cities_community_data %>% dplyr::select(city_name) %>% distinct() %>% as.list()
$city_name
 [1] "San Jose"                 "Los Angeles"              "Concord"                  "Tijuana"                  "Bakersfield"             
 [6] "Fresno"                   "Sacramento"               "Mexicali"                 "Hermosillo"               "Las Vegas"               
[11] "Phoenix"                  "Tucson"                   "Durango"                  "Portland"                 "Chihuahua"               
[16] "Aguascalientes"           "Seattle"                  "Ciudad Juárez"            "San Luis Potosí"          "Mexico City"             
[21] "Saltillo"                 "Vancouver"                "Salt Lake City"           "Albuquerque"              "Monterrey"               
[26] "Nuevo Laredo"             "San Antonio"              "Denver"                   "Austin"                   "Houston"                 
[31] "Dallas"                   "Oklahoma City"            "Calgary"                  "New Orleans"              "Kansas City"             
[36] "Omaha"                    "St. Louis"                "Bradenton"                "Tampa"                    "Minneapolis [Saint Paul]"
[41] "Atlanta"                  "Orlando"                  "Louisville"               "Chicago"                  "Indianapolis"            
[46] "Milwaukee"               

attr(,"na.action")
  1  56  81  87  90  92  94  96  98 100 102 103 104 106 112 115 116 117 118 119 121 215 
  1  56  81  87  90  92  94  96  98 100 102 103 104 106 112 115 116 117 118 119 121 215 
attr(,"class")
[1] "omit"
nearctic_cities_nmds = community_nmds(communities %>% filter(city_id %in% nearctic_cities_community_data$city_id)) 
Run 0 stress 0.1005678 
Run 1 stress 0.1000046 
... New best solution
... Procrustes: rmse 0.007231129  max resid 0.03470438 
Run 2 stress 0.1007176 
Run 3 stress 0.1017503 
Run 4 stress 0.1017503 
Run 5 stress 0.1217441 
Run 6 stress 0.1017503 
Run 7 stress 0.121519 
Run 8 stress 0.1210168 
Run 9 stress 0.1017503 
Run 10 stress 0.1000046 
... New best solution
... Procrustes: rmse 0.000008641127  max resid 0.00003146381 
... Similar to previous best
Run 11 stress 0.1217145 
Run 12 stress 0.1212382 
Run 13 stress 0.1323942 
Run 14 stress 0.102946 
Run 15 stress 0.1017503 
Run 16 stress 0.1017503 
Run 17 stress 0.1000046 
... Procrustes: rmse 0.000005711568  max resid 0.00001968977 
... Similar to previous best
Run 18 stress 0.1217145 
Run 19 stress 0.1005678 
Run 20 stress 0.1007176 
*** Best solution repeated 2 times
nearctic_cities_nmds
scree_plot(nearctic_cities_nmds)

nearctic_cities = cluster_cities(city_nmds = nearctic_cities_nmds, cities_community_data = nearctic_cities_community_data, centers = 4)
Joining with `by = join_by(city_id)`
plot_nmds_clusters(nearctic_cities)

nearctic_biomes = st_crop(resolve[resolve$REALM == 'Nearctic',c('REALM')], xmin = -220, ymin = 0, xmax = 0, ymax = 70)
although coordinates are longitude/latitude, st_intersection assumes that they are planar
Warning: attribute variables are assumed to be spatially constant throughout all geometries
 
ggplot() + 
  geom_sf(data = nearctic_biomes, aes(geometry = geometry)) + 
  geom_sf(data = nearctic_cities, aes(geometry = geometry, color = cluster))

ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'neartic_clusters.jpg'))
Saving 7.29 x 4.51 in image

test_value('Nearctic MNTD', nearctic_cities$mntd_normalised)
Warning: cannot compute exact p-value with ties
[1] "Nearctic MNTD mean 0.6 "
test_value('Nearctic beak gape FDiv', nearctic_cities$gape_width_fdiv_normalised)
Warning: cannot compute exact p-value with ties
[1] "Nearctic beak gape FDiv mean 0.59 "
test_value('Nearctic HWI FDiv', nearctic_cities$handwing_index_fdiv_normalised)
Warning: cannot compute exact p-value with ties
[1] "Nearctic HWI FDiv mean 0.31 **"
nrow(nearctic_cities)
[1] 46

Neartic Cluster 1`

nearactic_cluster1 = nearctic_cities %>% filter(cluster == 1)
plot_city_cluster(nearactic_cluster1, 'Neartic cluster 1')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'neartic_cluster1.jpg'), width = get_image_width(nearactic_cluster1), height = get_image_height(nearactic_cluster1), units = "mm")

Neartic Cluster 2

nearactic_cluster2 = nearctic_cities %>% filter(cluster == 2)
plot_city_cluster(nearactic_cluster2, 'Neartic cluster 2')
Warning: cannot compute exact p-value with tiesWarning: cannot compute exact p-value with tiesWarning: cannot compute exact p-value with tiesWarning: cannot compute exact p-value with ties
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'neartic_cluster2.jpg'), width = get_image_width(nearactic_cluster2), height = get_image_height(nearactic_cluster2), units = "mm")

Neartic Cluster 3

nearactic_cluster3 = nearctic_cities %>% filter(cluster == 3)
plot_city_cluster(nearactic_cluster3, 'Neartic cluster 3')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'neartic_cluster3.jpg'), width = get_image_width(nearactic_cluster3), height = get_image_height(nearactic_cluster3), units = "mm")

Neartic Cluster 4

nearactic_cluster4 = nearctic_cities %>% filter(cluster == 4)
plot_city_cluster(nearactic_cluster4, 'Neartic cluster 4')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'neartic_cluster4.jpg'), width = get_image_width(nearactic_cluster4), height = get_image_height(nearactic_cluster4), units = "mm")

Neotropic

neotropic_cities_community_data = community_data_metrics %>% filter(core_realm == 'Neotropic')
neotropic_cities_community_data %>% dplyr::select(city_name) %>% distinct() %>% as.list()
$city_name
 [1] "Culiacán"                  "Guadalajara"               "Morelia"                   "Acapulco"                  "Querétaro"                
 [6] "Cuernavaca"                "Puebla"                    "Oaxaca"                    "Xalapa"                    "Veracruz"                 
[11] "Tuxtla Gutiérrez"          "Villahermosa"              "Guatemala City"            "San Salvador"              "San Pedro Sula"           
[16] "Mérida"                    "Tegucigalpa"               "Managua"                   "San José"                  "Cancún"                   
[21] "Guayaquil"                 "Chiclayo"                  "Panama City"               "Trujillo"                  "Quito"                    
[26] "Havana"                    "Cali"                      "Lima"                      "Pereira"                   "Miami"                    
[31] "Medellín"                  "Ibagué"                    "Cartagena"                 "Kingston"                  "Bogota"                   
[36] "Barranquilla"              "Bucaramanga"               "Cúcuta"                    "Maracaibo"                 "Arequipa"                 
[41] "Barquisimeto"              "Santo Domingo"             "Maracay"                   "El Alto [La Paz]"          "Caracas"                  
[46] "Cochabamba"                "Viña del Mar [Valparaíso]" "Río Piedras [San Juan]"    "Barcelona"                 "Concepción"               
[51] "Santiago"                  "Mendoza"                   "Salta"                     "Cordoba"                   "Asuncion"                 
[56] "Buenos Aires"              "La Plata"                  "Ciudad del Este"           "Montevideo"                "Mar del Plata"            
[61] "Porto Alegre"              "São Paulo"                 "Santos"                    "Sao Jose dos Campos"      

attr(,"na.action")
  1  56  81  87  90  92  94  96  98 100 102 103 104 106 112 115 116 117 118 119 121 215 
  1  56  81  87  90  92  94  96  98 100 102 103 104 106 112 115 116 117 118 119 121 215 
attr(,"class")
[1] "omit"
neotropic_cities_nmds = community_nmds(communities %>% filter(city_id %in% neotropic_cities_community_data$city_id)) 
Run 0 stress 0.134619 
Run 1 stress 0.1405831 
Run 2 stress 0.1402343 
Run 3 stress 0.1346226 
... Procrustes: rmse 0.001163646  max resid 0.007319248 
... Similar to previous best
Run 4 stress 0.1414222 
Run 5 stress 0.134433 
... New best solution
... Procrustes: rmse 0.00682457  max resid 0.04571633 
Run 6 stress 0.1344509 
... Procrustes: rmse 0.001186994  max resid 0.006732139 
... Similar to previous best
Run 7 stress 0.134433 
... New best solution
... Procrustes: rmse 0.00001887778  max resid 0.00006224176 
... Similar to previous best
Run 8 stress 0.1406946 
Run 9 stress 0.1346225 
... Procrustes: rmse 0.007127633  max resid 0.04569628 
Run 10 stress 0.1346406 
... Procrustes: rmse 0.007281613  max resid 0.04569614 
Run 11 stress 0.134433 
... Procrustes: rmse 0.00009355504  max resid 0.0002972054 
... Similar to previous best
Run 12 stress 0.1405822 
Run 13 stress 0.1405822 
Run 14 stress 0.1346226 
... Procrustes: rmse 0.007186606  max resid 0.04568917 
Run 15 stress 0.1346226 
... Procrustes: rmse 0.007196645  max resid 0.04566584 
Run 16 stress 0.134619 
... Procrustes: rmse 0.0068466  max resid 0.04566077 
Run 17 stress 0.134433 
... New best solution
... Procrustes: rmse 0.00001770911  max resid 0.00005326707 
... Similar to previous best
Run 18 stress 0.1346369 
... Procrustes: rmse 0.006925503  max resid 0.04573761 
Run 19 stress 0.141222 
Run 20 stress 0.1348046 
... Procrustes: rmse 0.006551197  max resid 0.04607941 
*** Best solution repeated 1 times
neotropic_cities_nmds
scree_plot(neotropic_cities_nmds)

neotropic_cities = cluster_cities(city_nmds = neotropic_cities_nmds, cities_community_data = neotropic_cities_community_data, centers = 5)
Joining with `by = join_by(city_id)`
plot_nmds_clusters(neotropic_cities)

neotropic_biomes = resolve[resolve$REALM == 'Neotropic',c('REALM')]
 
ggplot() + 
  geom_sf(data = neotropic_biomes, aes(geometry = geometry)) + 
  geom_sf(data = neotropic_cities, aes(geometry = geometry, color = cluster))
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'neotropic_clusters.jpg'))
Saving 7.29 x 4.51 in image

test_value('Neotropic MNTD', neotropic_cities$mntd_normalised)
[1] "Neotropic MNTD mean 0.53 "
test_value('Neotropic beak gape FDiv', neotropic_cities$gape_width_fdiv_normalised)
[1] "Neotropic beak gape FDiv mean 0.44 "
test_value('Neotropic HWI FDiv', neotropic_cities$handwing_index_fdiv_normalised)
[1] "Neotropic HWI FDiv mean 0.53 "
nrow(neotropic_cities)
[1] 64

Neotropic Cluster 1

neotropic_cluster1 = neotropic_cities %>% filter(cluster == 1)
plot_city_cluster(neotropic_cluster1, 'Neotropic cluster 1')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'neotropic_cluster1.jpg'), width = get_image_width(neotropic_cluster1), height = get_image_height(neotropic_cluster1), units = "mm")

Neotropic Cluster 2

neotropic_cluster2 = neotropic_cities %>% filter(cluster == 2)
plot_city_cluster(neotropic_cluster2, 'Neotropic cluster 2')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'neotropic_cluster2.jpg'), width = get_image_width(neotropic_cluster2), height = get_image_height(neotropic_cluster2), units = "mm")

Neotropic Cluster 3

neotropic_cluster3 = neotropic_cities %>% filter(cluster == 3)
plot_city_cluster(neotropic_cluster3, 'Neotropic cluster 3')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'neotropic_cluster3.jpg'), width = get_image_width(neotropic_cluster3), height = get_image_height(neotropic_cluster3), units = "mm")

Neotropic Cluster 4

neotropic_cluster4 = neotropic_cities %>% filter(cluster == 4)
plot_city_cluster(neotropic_cluster4, 'Neotropic cluster 4')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'neotropic_cluster4.jpg'), width = get_image_width(neotropic_cluster4), height = get_image_height(neotropic_cluster4), units = "mm")

Neotropic Cluster 5

neotropic_cluster5 = neotropic_cities %>% filter(cluster == 5)
plot_city_cluster(neotropic_cluster5, 'Neotropic cluster 5')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'neotropic_cluster5.jpg'), width = get_image_width(neotropic_cluster5), height = get_image_height(neotropic_cluster5), units = "mm")

Palearctic

palearctic_cities_community_data = community_data_metrics %>% filter(core_realm == 'Palearctic')
palearctic_cities_community_data %>% dplyr::select(city_name) %>% distinct() %>% as.list()
$city_name
 [1] "Lisbon"                "Porto"                 "Marrakesh"             "Seville"               "Dublin"                "Málaga"               
 [7] "Madrid"                "Glasgow"               "Bilbao"                "Liverpool"             "Bristol"               "Manchester"           
[13] "Birmingham"            "Leeds"                 "Newcastle upon Tyne"   "Sheffield"             "Nottingham"            "Valencia"             
[19] "London"                "Toulouse"              "Paris"                 "Barcelona"             "Rotterdam [The Hague]" "Brussels"             
[25] "Amsterdam"             "Lyon"                  "Marseille"             "Dusseldorf"            "Nice"                  "Frankfurt am Main"    
[31] "Zurich"                "Oslo"                  "Stuttgart"             "Hamburg"               "Genoa"                 "Nuremberg"            
[37] "Copenhagen"            "Munich"                "Berlin"                "Dresden"               "Rome"                  "Prague"               
[43] "Stockholm"             "Poznan"                "Vienna"                "Wroclaw"               "Zagreb"                "Gdansk"               
[49] "Budapest"              "Krakow"                "Warsaw"                "Helsinki"              "Riga"                  "Belgrade"             
[55] "Lviv"                  "Sofia"                 "Thessaloniki"          "Saint Petersburg"      "Minsk"                 "Athens"               
[61] "Kyiv"                  "Istanbul"              "Odesa"                 "Samsun"                "Luxor"                 "Tel Aviv"             
[67] "Jerusalem"             "Tbilisi"               "Yerevan"               "Kuwait City"           "Doha"                  "Abu Dhabi"            
[73] "Dubai"                 "Bishkek"              

attr(,"na.action")
  1  56  81  87  90  92  94  96  98 100 102 103 104 106 112 115 116 117 118 119 121 215 
  1  56  81  87  90  92  94  96  98 100 102 103 104 106 112 115 116 117 118 119 121 215 
attr(,"class")
[1] "omit"
palearctic_cities_nmds = community_nmds(communities %>% filter(city_id %in% palearctic_cities_community_data$city_id)) 
Run 0 stress 0.04961857 
Run 1 stress 0.07367425 
Run 2 stress 0.05453968 
Run 3 stress 0.0712512 
Run 4 stress 0.05826357 
Run 5 stress 0.06338732 
Run 6 stress 0.06304725 
Run 7 stress 0.08618201 
Run 8 stress 0.07925636 
Run 9 stress 0.05647129 
Run 10 stress 0.09129792 
Run 11 stress 0.0497659 
... Procrustes: rmse 0.005703935  max resid 0.01420466 
Run 12 stress 0.05000939 
... Procrustes: rmse 0.06743721  max resid 0.2226243 
Run 13 stress 0.07603764 
Run 14 stress 0.0500095 
... Procrustes: rmse 0.06741103  max resid 0.2225553 
Run 15 stress 0.096573 
Run 16 stress 0.0556821 
Run 17 stress 0.05115583 
Run 18 stress 0.08625966 
Run 19 stress 0.06766821 
Run 20 stress 0.06829368 
Run 21 stress 0.07723847 
Run 22 stress 0.05236354 
Run 23 stress 0.05642252 
Run 24 stress 0.05235961 
Run 25 stress 0.06829346 
Run 26 stress 0.05353311 
Run 27 stress 0.06421851 
Run 28 stress 0.05706493 
Run 29 stress 0.0512458 
Run 30 stress 0.05479168 
*** Best solution was not repeated -- monoMDS stopping criteria:
    14: no. of iterations >= maxit
    14: stress ratio > sratmax
     2: scale factor of the gradient < sfgrmin
palearctic_cities_nmds
scree_plot(palearctic_cities_nmds)
Warning: Quick-TRANSfer stage steps exceeded maximum (= 3700)

palearctic_cities = cluster_cities(city_nmds = palearctic_cities_nmds, cities_community_data = palearctic_cities_community_data, centers = 7)
Joining with `by = join_by(city_id)`
plot_nmds_clusters(palearctic_cities)

palearctic_biomes = st_crop(resolve[resolve$REALM == 'Palearctic',c('REALM')], xmin = -30, ymin = 20, xmax = 80, ymax = 65)
although coordinates are longitude/latitude, st_intersection assumes that they are planar
Warning: attribute variables are assumed to be spatially constant throughout all geometries
 
ggplot() + 
  geom_sf(data = palearctic_biomes, aes(geometry = geometry)) + 
  geom_sf(data = palearctic_cities, aes(geometry = geometry, color = cluster))
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'palearctic_clusters.jpg'))
Saving 7.29 x 4.51 in image

test_value('Palearctic MNTD', palearctic_cities$mntd_normalised)
[1] "Palearctic MNTD mean 0.58 *"
test_value('Palearctic beak gape FDiv', palearctic_cities$gape_width_fdiv_normalised)
[1] "Palearctic beak gape FDiv mean 0.86 ***"
test_value('Palearctic HWI FDiv', palearctic_cities$handwing_index_fdiv_normalised)
[1] "Palearctic HWI FDiv mean 0.51 "
nrow(palearctic_cities)
[1] 74

Palearctic Cluster 1

palearctic_cluster1 = palearctic_cities %>% filter(cluster == 1)

plot_city_cluster(palearctic_cluster1, 'Palearctic cluster 1')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'palearctic_cluster1.jpg'), width = get_image_width(palearctic_cluster1), height = get_image_height(palearctic_cluster1), units = "mm")

Palearctic Cluster 2

palearctic_cluster2 = palearctic_cities %>% filter(cluster == 2)
plot_city_cluster(palearctic_cluster2, 'Palearctic cluster 2')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'palearctic_cluster2.jpg'), width = get_image_width(palearctic_cluster2), height = get_image_height(palearctic_cluster2), units = "mm")

Palearctic Cluster 3

palearctic_cluster3 = palearctic_cities %>% filter(cluster == 3)
plot_city_cluster(palearctic_cluster3, 'Palearctic cluster 3')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'palearctic_cluster3.jpg'), width = get_image_width(palearctic_cluster3), height = get_image_height(palearctic_cluster3), units = "mm")

Palearctic Cluster 4

palearctic_cluster4 = palearctic_cities %>% filter(cluster == 4)
plot_city_cluster(palearctic_cluster4, 'Palearctic cluster 4')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'palearctic_cluster4.jpg'), width = get_image_width(palearctic_cluster4), height = get_image_height(palearctic_cluster4), units = "mm")

Palearctic Cluster 5

palearctic_cluster5 = palearctic_cities %>% filter(cluster == 5)
nrow(palearctic_cluster5)
[1] 50
plot_city_cluster(palearctic_cluster5[1:25,], 'Palearctic cluster 5a')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'palearctic_cluster5.jpg'), width = get_image_width(palearctic_cluster5), height = get_image_height(palearctic_cluster5), units = "mm")

plot_city_cluster(palearctic_cluster5[25:50,], 'Palearctic cluster 5b')
Warning: cannot compute exact p-value with ties
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'palearctic_cluster5b.jpg'), width = get_image_width(palearctic_cluster5), height = get_image_height(palearctic_cluster5), units = "mm")

Palearctic Cluster 6

palearctic_cluster6 = palearctic_cities %>% filter(cluster == 6)
plot_city_cluster(palearctic_cluster6, 'Palearctic cluster 6')
Warning: cannot compute exact p-value with ties
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'palearctic_cluster6.jpg'), width = get_image_width(palearctic_cluster6), height = get_image_height(palearctic_cluster6), units = "mm")

Palearctic Cluster 7

palearctic_cluster7 = palearctic_cities %>% filter(cluster == 7)
plot_city_cluster(palearctic_cluster7, 'Palearctic cluster 7')
Warning: cannot compute exact p-value with ties
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'palearctic_cluster7.jpg'), width = get_image_width(palearctic_cluster7), height = get_image_height(palearctic_cluster7), units = "mm")

Afrotropic

afrotropic_cities_community_data = community_data_metrics %>% filter(core_realm == 'Afrotropic')
afrotropic_cities_community_data %>% dplyr::select(city_name) %>% distinct() %>% as.list()
$city_name
[1] "Cape Town"    "Johannesburg" "Pretoria"     "Kigali"       "Kampala"      "Arusha"       "Nairobi"      "Addis Ababa"  "Antananarivo"

attr(,"na.action")
  1  56  81  87  90  92  94  96  98 100 102 103 104 106 112 115 116 117 118 119 121 215 
  1  56  81  87  90  92  94  96  98 100 102 103 104 106 112 115 116 117 118 119 121 215 
attr(,"class")
[1] "omit"
afrotropic_cities_nmds = community_nmds(communities %>% filter(city_id %in% afrotropic_cities_community_data$city_id)) 
Run 0 stress 0.00009014786 
Run 1 stress 0.0007443902 
Run 2 stress 0.000377487 
... Procrustes: rmse 0.002074146  max resid 0.003340817 
... Similar to previous best
Run 3 stress 0.00008179516 
... New best solution
... Procrustes: rmse 0.0001660244  max resid 0.000361027 
... Similar to previous best
Run 4 stress 0.000656661 
Run 5 stress 0.3209238 
Run 6 stress 0.00009538562 
... Procrustes: rmse 0.00003771484  max resid 0.00007382498 
... Similar to previous best
Run 7 stress 0.00009646529 
... Procrustes: rmse 0.0002653617  max resid 0.0004782319 
... Similar to previous best
Run 8 stress 0.00009486991 
... Procrustes: rmse 0.0002901629  max resid 0.0004478019 
... Similar to previous best
Run 9 stress 0.00009857688 
... Procrustes: rmse 0.0004629411  max resid 0.0009418255 
... Similar to previous best
Run 10 stress 0.00008547616 
... Procrustes: rmse 0.00001297639  max resid 0.00002426197 
... Similar to previous best
Run 11 stress 0.00009878898 
... Procrustes: rmse 0.0004635066  max resid 0.0009419129 
... Similar to previous best
Run 12 stress 0.0005427305 
... Procrustes: rmse 0.002940686  max resid 0.004342951 
... Similar to previous best
Run 13 stress 0.001227374 
Run 14 stress 0.00009793901 
... Procrustes: rmse 0.0001331878  max resid 0.0002796824 
... Similar to previous best
Run 15 stress 0.00009902405 
... Procrustes: rmse 0.0001283685  max resid 0.0002454039 
... Similar to previous best
Run 16 stress 0.00009120045 
... Procrustes: rmse 0.00002743712  max resid 0.00005350613 
... Similar to previous best
Run 17 stress 0.00009094513 
... Procrustes: rmse 0.0002505202  max resid 0.00046142 
... Similar to previous best
Run 18 stress 0.00009137309 
... Procrustes: rmse 0.000123082  max resid 0.0002401071 
... Similar to previous best
Run 19 stress 0.00009536048 
... Procrustes: rmse 0.00003982921  max resid 0.00006129269 
... Similar to previous best
Run 20 stress 0.00006560657 
... New best solution
... Procrustes: rmse 0.0001813766  max resid 0.0003094943 
... Similar to previous best
*** Best solution repeated 1 times
Warning: stress is (nearly) zero: you may have insufficient data
afrotropic_cities_nmds
scree_plot(afrotropic_cities_nmds)

afrotropic_cities = cluster_cities(city_nmds = afrotropic_cities_nmds, cities_community_data = afrotropic_cities_community_data, centers = 2)
Joining with `by = join_by(city_id)`
plot_nmds_clusters(afrotropic_cities)

afrotropic_biomes = resolve[resolve$REALM == 'Afrotropic',c('REALM')]
 
ggplot() + 
  geom_sf(data = afrotropic_biomes, aes(geometry = geometry)) + 
  geom_sf(data = afrotropic_cities, aes(geometry = geometry, color = cluster))
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'afrotropic_clusters.jpg'))
Saving 7.29 x 4.51 in image

test_value('Afrotropic MNTD', afrotropic_cities$mntd_normalised)
[1] "Afrotropic MNTD mean 0.14 *"
test_value('Afrotropic beak gape FDiv', afrotropic_cities$gape_width_fdiv_normalised)
[1] "Afrotropic beak gape FDiv mean 0.41 "
test_value('Afrotropic HWI FDiv', afrotropic_cities$handwing_index_fdiv_normalised)
[1] "Afrotropic HWI FDiv mean 0.61 "
nrow(afrotropic_cities)
[1] 9

Afrotropic Cluster 1

afrotropic_cluster1 = afrotropic_cities %>% filter(cluster == 1)
plot_city_cluster(afrotropic_cluster1, 'Afrotropic cluster 1')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'afrotropic_cluster1.jpg'), width = get_image_width(afrotropic_cluster1), height = get_image_height(afrotropic_cluster1), units = "mm")

Afrotropic Cluster 2

afrotropic_cluster2 = afrotropic_cities %>% filter(cluster == 2)
plot_city_cluster(afrotropic_cluster2, 'Afrotropic cluster 2')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'afrotropic_cluster2.jpg'), width = get_image_width(afrotropic_cluster2), height = get_image_height(afrotropic_cluster2), units = "mm")

Indomalayan

indomalayan_cities_community_data = community_data_metrics %>% filter(core_realm == 'Indomalayan')
indomalayan_cities_community_data %>% dplyr::select(city_name) %>% distinct() %>% as.list()
$city_name
  [1] "Srinagar"            "Jamnagar"            "Jammu"               "Rajkot"              "Bikaner"             "Jodhpur"             "Jalandhar"          
  [8] "Ahmedabad"           "Bhavnagar"           "Ludhiana"            "Anand"               "Udaipur"             "Surat"               "Vadodara"           
 [15] "Ajmer"               "Chandigarh"          "Vasai-Virar"         "Mumbai"              "Jaipur"              "Delhi [New Delhi]"   "Nashik"             
 [22] "Dehradun"            "Kota"                "Pune"                "Haridwar"            "Dhule"               "Ujjain"              "Indore"             
 [29] "Ahmadnagar"          "Kolhapur"            "Jalgaon"             "Agra"                "Aurangabad"          "Sangli"              "Belagavi"           
 [36] "Gwalior"             "Budaun"              "Bareilly"            "Dharwad"             "Bhopal"              "Bhind"               "Mangaluru"          
 [43] "Solapur"             "Vijayapura"          "Akola"               "Latur"               "Kannur"              "Davanagere"          "Thalassery"         
 [50] "Amravati"            "Kalaburagi"          "Kozhikode"           "Guruvayur"           "Malappuram"          "Lucknow"             "Thrissur"           
 [57] "Mysuru"              "Kochi"               "Alappuzha"           "Nagpur"              "Kollam"              "Jabalpur"            "Ettumanoor"         
 [64] "Hyderabad"           "Coimbatore"          "Bengaluru"           "Thiruvananthapuram"  "Tiruppur"            "Faizabad"            "Erode"              
 [71] "Prayagraj"           "Pratapgarh"          "Salem"               "Dindigul"            "Madurai"             "Tiruchirappalli"     "Durg"               
 [78] "Vellore"             "Tirupati"            "Raipur"              "Bilaspur"            "Vijayawada"          "Puducherry"          "Chennai"            
 [85] "Kathmandu"           "Colombo"             "Rajamahendravaram"   "Patna"               "Kandy"               "Bihar Sharif"        "Visakhapatnam"      
 [92] "Ranchi"              "Brahmapur"           "Jamshedpur"          "Darjeeling"          "Siliguri"            "Cuttack"             "Bhubaneshwar"       
 [99] "Jalpaiguri"          "Berhampore"          "Kolkata"             "Krishnanagar"        "Guwahati [Dispur]"   "Agartala"            "Silchar"            
[106] "Dimapur"             "Bangkok"             "George Town"         "Kuala Lumpur"        "Phnom Penh"          "Singapore"           "Hong Kong"          
[113] "Sha Tin"             "Hsinchu"             "Taichung"            "New Taipei [Taipei]" "Tainan"              "Denpasar"            "Kaohsiung"          
[120] "Kota Kinabalu"      

attr(,"na.action")
  1  56  81  87  90  92  94  96  98 100 102 103 104 106 112 115 116 117 118 119 121 215 
  1  56  81  87  90  92  94  96  98 100 102 103 104 106 112 115 116 117 118 119 121 215 
attr(,"class")
[1] "omit"
indomalayan_cities_nmds = community_nmds(communities %>% filter(city_id %in% indomalayan_cities_community_data$city_id)) 
Run 0 stress 0.1190668 
Run 1 stress 0.1224401 
Run 2 stress 0.1394961 
Run 3 stress 0.1161598 
... New best solution
... Procrustes: rmse 0.02798297  max resid 0.2346031 
Run 4 stress 0.1540252 
Run 5 stress 0.1175538 
Run 6 stress 0.1167657 
Run 7 stress 0.1384412 
Run 8 stress 0.1241275 
Run 9 stress 0.1163498 
... Procrustes: rmse 0.025336  max resid 0.2481842 
Run 10 stress 0.1153501 
... New best solution
... Procrustes: rmse 0.008287738  max resid 0.08217213 
Run 11 stress 0.1199235 
Run 12 stress 0.1172201 
Run 13 stress 0.151756 
Run 14 stress 0.1246307 
Run 15 stress 0.134023 
Run 16 stress 0.1580175 
Run 17 stress 0.1229198 
Run 18 stress 0.1502633 
Run 19 stress 0.1189366 
Run 20 stress 0.117072 
Run 21 stress 0.1458634 
Run 22 stress 0.137476 
Run 23 stress 0.1186188 
Run 24 stress 0.1637221 
Run 25 stress 0.1191928 
Run 26 stress 0.1164559 
Run 27 stress 0.142366 
Run 28 stress 0.1379337 
Run 29 stress 0.1191853 
Run 30 stress 0.1266232 
*** Best solution was not repeated -- monoMDS stopping criteria:
    29: stress ratio > sratmax
     1: scale factor of the gradient < sfgrmin
indomalayan_cities_nmds
scree_plot(indomalayan_cities_nmds)

indomalayan_cities = cluster_cities(city_nmds = indomalayan_cities_nmds, cities_community_data = indomalayan_cities_community_data, centers = 5)
Joining with `by = join_by(city_id)`
plot_nmds_clusters(indomalayan_cities)

indomalayan_biomes = resolve[resolve$REALM == 'Indomalayan',c('REALM')]
 
ggplot() + 
  geom_sf(data = indomalayan_biomes, aes(geometry = geometry)) + 
  geom_sf(data = indomalayan_cities, aes(geometry = geometry, color = cluster))
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'indomalayan_clusters.jpg'))
Saving 7.29 x 4.51 in image

test_value('Indomalayan MNTD', indomalayan_cities$mntd_normalised)
[1] "Indomalayan MNTD mean 0.44 ***"
test_value('Indomalayan beak gape FDiv', indomalayan_cities$gape_width_fdiv_normalised)
[1] "Indomalayan beak gape FDiv mean 0.52 "
test_value('Indomalayan HWI FDiv', indomalayan_cities$handwing_index_fdiv_normalised)
[1] "Indomalayan HWI FDiv mean 0.82 ***"
nrow(indomalayan_cities)
[1] 120

Indomalayan Cluster 1

indomalayan_cluster1 = indomalayan_cities %>% filter(cluster == 1)
plot_city_cluster(indomalayan_cluster1, 'Indomalayan cluster 1')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'indomalayan_cluster1.jpg'), width = get_image_width(indomalayan_cluster1), height = get_image_height(indomalayan_cluster1), units = "mm")

Indomalayan Cluster 2

indomalayan_cluster2 = indomalayan_cities %>% filter(cluster == 2)
plot_city_cluster(indomalayan_cluster2, 'Indomalayan cluster 2')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'indomalayan_cluster2.jpg'), width = get_image_width(indomalayan_cluster2), height = get_image_height(indomalayan_cluster2), units = "mm")

Indomalayan Cluster 3

indomalayan_cluster3 = indomalayan_cities %>% filter(cluster == 3)
plot_city_cluster(indomalayan_cluster3, 'Indomalayan cluster 3')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'indomalayan_cluster3.jpg'), width = get_image_width(indomalayan_cluster3), height = get_image_height(indomalayan_cluster3), units = "mm")

Indomalayan Cluster 4

indomalayan_cluster4 = indomalayan_cities %>% filter(cluster == 4)
plot_city_cluster(indomalayan_cluster4, 'Indomalayan cluster 4')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'indomalayan_cluster4.jpg'), width = get_image_width(indomalayan_cluster4), height = get_image_height(indomalayan_cluster4), units = "mm")

Indomalayan Cluster 5

indomalayan_cluster5 = indomalayan_cities %>% filter(cluster == 5)
plot_city_cluster(indomalayan_cluster5, 'Indomalayan cluster 5')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'indomalayan_cluster5.jpg'), width = get_image_width(indomalayan_cluster5), height = get_image_height(indomalayan_cluster5), units = "mm")

Australasia

australasia_cities_community_data = community_data_metrics %>% filter(core_realm == 'Australasia')
australasia_cities_community_data %>% dplyr::select(city_name) %>% distinct() %>% as.list()
$city_name
[1] "Perth"     "Adelaide"  "Melbourne" "Sydney"    "Brisbane"  "Auckland" 

attr(,"na.action")
  1  56  81  87  90  92  94  96  98 100 102 103 104 106 112 115 116 117 118 119 121 215 
  1  56  81  87  90  92  94  96  98 100 102 103 104 106 112 115 116 117 118 119 121 215 
attr(,"class")
[1] "omit"
australasia_cities_nmds = community_nmds(communities %>% filter(city_id %in% australasia_cities_community_data$city_id)) 
Run 0 stress 0 
Run 1 stress 0 
... Procrustes: rmse 0.1120339  max resid 0.1622729 
Run 2 stress 0.1572548 
Run 3 stress 0 
... Procrustes: rmse 0.07245479  max resid 0.1129183 
Run 4 stress 0 
... Procrustes: rmse 0.04603582  max resid 0.07090683 
Run 5 stress 0 
... Procrustes: rmse 0.2182675  max resid 0.295989 
Run 6 stress 0 
... Procrustes: rmse 0.133075  max resid 0.2072423 
Run 7 stress 0.0000995744 
... Procrustes: rmse 0.1149167  max resid 0.1742941 
Run 8 stress 0 
... Procrustes: rmse 0.1694089  max resid 0.2635754 
Run 9 stress 0 
... Procrustes: rmse 0.1063225  max resid 0.1577545 
Run 10 stress 0 
... Procrustes: rmse 0.1313955  max resid 0.2135724 
Run 11 stress 0 
... Procrustes: rmse 0.1251736  max resid 0.1780552 
Run 12 stress 0 
... Procrustes: rmse 0.05487419  max resid 0.08977988 
Run 13 stress 0.1572548 
Run 14 stress 0.00006459844 
... Procrustes: rmse 0.1484605  max resid 0.2073383 
Run 15 stress 0.00008456015 
... Procrustes: rmse 0.1937182  max resid 0.3430883 
Run 16 stress 0 
... Procrustes: rmse 0.09381547  max resid 0.1446598 
Run 17 stress 0.1572548 
Run 18 stress 0.00001782659 
... Procrustes: rmse 0.132877  max resid 0.2076538 
Run 19 stress 0 
... Procrustes: rmse 0.115211  max resid 0.1946323 
Run 20 stress 0 
... Procrustes: rmse 0.1542908  max resid 0.2134235 
Run 21 stress 0.1572548 
Run 22 stress 0 
... Procrustes: rmse 0.1239607  max resid 0.190374 
Run 23 stress 0.00007261303 
... Procrustes: rmse 0.123741  max resid 0.1717834 
Run 24 stress 0.00006062953 
... Procrustes: rmse 0.1354649  max resid 0.2269844 
Run 25 stress 0.00008963805 
... Procrustes: rmse 0.1180163  max resid 0.1705588 
Run 26 stress 0.000009458018 
... Procrustes: rmse 0.1301844  max resid 0.1953992 
Run 27 stress 0.2349416 
Run 28 stress 0.0000881637 
... Procrustes: rmse 0.07152338  max resid 0.1085942 
Run 29 stress 0 
... Procrustes: rmse 0.09917667  max resid 0.1266893 
Run 30 stress 0 
... Procrustes: rmse 0.08881426  max resid 0.1290499 
*** Best solution was not repeated -- monoMDS stopping criteria:
    25: stress < smin
     1: stress ratio > sratmax
     4: scale factor of the gradient < sfgrmin
Warning: stress is (nearly) zero: you may have insufficient data
australasia_cities_nmds
scree_plot(australasia_cities_nmds)

australasia_cities = cluster_cities(city_nmds = australasia_cities_nmds, cities_community_data = australasia_cities_community_data, centers = 3)
Joining with `by = join_by(city_id)`
plot_nmds_clusters(australasia_cities)

australasia_biomes = st_crop(resolve[resolve$REALM == 'Australasia',c('REALM')], xmin = 80, ymin = -70, xmax = 180, ymax = 20)
although coordinates are longitude/latitude, st_intersection assumes that they are planar
Warning: attribute variables are assumed to be spatially constant throughout all geometries
 
ggplot() + 
  geom_sf(data = australasia_biomes, aes(geometry = geometry)) + 
  geom_sf(data = australasia_cities, aes(geometry = geometry, color = cluster))
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'australasia_clusters.jpg'))
Saving 7.29 x 4.51 in image

test_value('Australasia MNTD', australasia_cities$mntd_normalised)
[1] "Australasia MNTD mean 0.48 "
test_value('Australasia beak gape FDiv', australasia_cities$gape_width_fdiv_normalised)
[1] "Australasia beak gape FDiv mean 0.5 "
test_value('Australasia HWI FDiv', australasia_cities$handwing_index_fdiv_normalised)
[1] "Australasia HWI FDiv mean 0.82 "
nrow(australasia_cities)
[1] 6

Australasia Cluster 1

australasia_cluster1 = australasia_cities %>% filter(cluster == 1)
plot_city_cluster(australasia_cluster1, 'Australasia cluster 1')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'australasia_cluster1.jpg'), width = get_image_width(australasia_cluster1), height = get_image_height(australasia_cluster1), units = "mm")

australasia_cluster2 = australasia_cities %>% filter(cluster == 2)
plot_city_cluster(australasia_cluster2, 'Australasia cluster 2')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'australasia_cluster2.jpg'), width = get_image_width(australasia_cluster2), height = get_image_height(australasia_cluster2), units = "mm")

australasia_cluster3 = australasia_cities %>% filter(cluster == 3)
plot_city_cluster(australasia_cluster3, 'Australasia cluster 3')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'australasia_cluster3.jpg'), width = get_image_width(australasia_cluster3), height = get_image_height(australasia_cluster3), units = "mm")

LS0tCnRpdGxlOiAiRGVlcCBkaXZlIHJlZ2lvbmFsIGNvbW11bml0aWVzIC0gdXNpbmcgcmVsYXRpdmUgYWJ1bmRhbmNlIHByb3giCm91dHB1dDogaHRtbF9ub3RlYm9vawpiaWJsaW9ncmFwaHk6IC4uL3JlZi5iaWIgIAotLS0KCmBgYHtyfQpzb3VyY2UoJy4uL2Vudi5SJykKYGBgCgojIFNwZWNpZXMgaW4gY29tbXVuaXRpZXMKSXQgc2VlbXMgcmVhc29uYWJsZSB0byBleHBlY3QgdGhhdCBjaXRpZXMgd2l0aCBzaW1pYWxyIHJlZ2lvbmFsIHBvb2xzIHdpbGwgaGF2ZSBzaW1pbGFyIHNwZWNpZXMgZW50ZXJpbmcgdGhlIGNpdHksIGFuZCB0aHVzIGEgc2ltaWxhciByZXNwb25zZSB0byB1cmJhbmlzYXRpb24uCgojIyBMb2FkIGRhdGEKYGBge3J9CmNpdHlfZWZmb3J0ID0gcmVhZF9jc3YoZmlsZW5hbWUoQ0lUWV9EQVRBX09VVFBVVF9ESVIsICdjaXR5X2VmZm9ydC5jc3YnKSkKY2l0eV9lZmZvcnQKYGBgCgpgYGB7cn0KY29tbXVuaXRpZXMgPSByZWFkX2NzdihmaWxlbmFtZShDT01NVU5JVFlfT1VUUFVUX0RJUiwgJ2NvbW11bml0aWVzX2Zvcl9hbmFseXNpcy5jc3YnKSkKCmNvbW11bml0aWVzX3N1bW1hcnkgPSBjb21tdW5pdGllcyAlPiUgZ3JvdXBfYnkoY2l0eV9pZCkgJT4lIHN1bW1hcmlzZSgKICByZWdpb25hbF9wb29sX3NpemUgPSBuKCksIAogIHVyYmFuX3Bvb2xfc2l6ZSA9IHN1bShyZWxhdGl2ZV9hYnVuZGFuY2VfcHJveHkgPiAwKQopICU+JSBsZWZ0X2pvaW4oY2l0eV9lZmZvcnQgJT4lIGRwbHlyOjpzZWxlY3QoY2l0eV9pZCwgcGVyY2VudGFnZV90b3RhbF9jaXR5X2FyZWFfc3VydmV5ZWQpKQpgYGAKCmBgYHtyfQpnZ3Bsb3QoY29tbXVuaXRpZXMgJT4lIGZpbHRlcihyZWxhdGl2ZV9hYnVuZGFuY2VfcHJveHkgPiAwKSwgYWVzKHggPSByZWxhdGl2ZV9hYnVuZGFuY2VfcHJveHkpKSArIGdlb21fYmFyKHN0YXQgPSAiYmluIikKYGBgCgpgYGB7cn0KY2l0eV9wb2ludHMgPSBzdF9jZW50cm9pZChyZWFkX3NmKGZpbGVuYW1lKENJVFlfREFUQV9PVVRQVVRfRElSLCAnY2l0eV9zZWxlY3Rpb24uc2hwJykpKQpgYGAKCmBgYHtyfQpjb21tdW5pdHlfZGF0YV9tZXRyaWNzID0gcmVhZF9jc3YoZmlsZW5hbWUoQ09NTVVOSVRZX09VVFBVVF9ESVIsICdjb21tdW5pdHlfYXNzZW1ibHlfbWV0cmljc191c2luZ19yZWxhdGl2ZV9hYnVuZGFuY2UuY3N2JykpICU+JQogIGRwbHlyOjpzZWxlY3QoY2l0eV9pZCwgbW50ZF9ub3JtYWxpc2VkLCBmZGl2X25vcm1hbGlzZWQsIG1hc3NfZmRpdl9ub3JtYWxpc2VkLCBoYW5kd2luZ19pbmRleF9mZGl2X25vcm1hbGlzZWQsIHRyb3BoaWNfdHJhaXRfZmRpdl9ub3JtYWxpc2VkLCBnYXBlX3dpZHRoX2ZkaXZfbm9ybWFsaXNlZCkgJT4lCiAgbGVmdF9qb2luKHJlYWRfY3N2KGZpbGVuYW1lKENJVFlfREFUQV9PVVRQVVRfRElSLCAncmVhbG1zLmNzdicpKSkgJT4lCiAgbGVmdF9qb2luKGNvbW11bml0aWVzX3N1bW1hcnkpICU+JQogIGxlZnRfam9pbihjaXR5X3BvaW50c1ssYygnY2l0eV9pZCcsICdjaXR5X25tJyldKSAlPiUKICByZW5hbWUoY2l0eV9uYW1lPSdjaXR5X25tJykgJT4lCiAgbmEub21pdCgpICU+JQogIGFycmFuZ2UoY2l0eV9pZCkKCmNvbW11bml0eV9kYXRhX21ldHJpY3MKYGBgCgpgYGB7cn0KY29tbXVuaXR5X2RhdGFfbWV0cmljcyAlPiUgZ3JvdXBfYnkoY29yZV9yZWFsbSkgJT4lIHN1bW1hcmlzZSh0b3RhbF9jaXRpZXMgPSBuKCkpCmBgYAoKYGBge3J9CnRlc3RfdmFsdWUgPSBmdW5jdGlvbihuYW1lLCBub3JtYWxpc2VkX2xpc3QpIHsKICB3aWxjb3hfdGVzdF9yZXN1bHQgPSB3aWxjb3gudGVzdChub3JtYWxpc2VkX2xpc3QsIG11ID0gMC41KQogIAogIHNpZ25pZmljYW5jZSA9IGlmZWxzZSh3aWxjb3hfdGVzdF9yZXN1bHQkcC52YWx1ZSA8IDAuMDAwMSwgJyoqKicsIAogICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2Uod2lsY294X3Rlc3RfcmVzdWx0JHAudmFsdWUgPCAwLjAwMSwgJyoqJywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2Uod2lsY294X3Rlc3RfcmVzdWx0JHAudmFsdWUgPCAwLjAxLCAnKicsICcnKSkpCiAgbSA9IG1lYW4obm9ybWFsaXNlZF9saXN0KQogIAogIHBhc3RlKG5hbWUsICdtZWFuJywgcm91bmQobSwgMiksIHNpZ25pZmljYW5jZSkKfQpgYGAKCmBgYHtyfQp0ZXN0X3ZhbHVlKCdHbG9iYWwgTU5URCcsIGNvbW11bml0eV9kYXRhX21ldHJpY3MkbW50ZF9ub3JtYWxpc2VkKQp0ZXN0X3ZhbHVlKCdHbG9iYWwgYmVhayBnYXBlIEZEaXYnLCBjb21tdW5pdHlfZGF0YV9tZXRyaWNzJGdhcGVfd2lkdGhfZmRpdl9ub3JtYWxpc2VkKQp0ZXN0X3ZhbHVlKCdHbG9iYWwgSFdJIEZEaXYnLCBjb21tdW5pdHlfZGF0YV9tZXRyaWNzJGhhbmR3aW5nX2luZGV4X2ZkaXZfbm9ybWFsaXNlZCkKbnJvdyhjb21tdW5pdHlfZGF0YV9tZXRyaWNzKQpgYGAKCkxvYWQgdHJhaXQgZGF0YQpgYGB7cn0KdHJhaXRzID0gcmVhZF9jc3YoZmlsZW5hbWUoVEFYT05PTVlfT1VUUFVUX0RJUiwgJ3RyYWl0c19qZXR6LmNzdicpKQpoZWFkKHRyYWl0cykKYGBgCgpgYGB7cn0KZmV0Y2hfbm9ybWFsaXNlZF90cmFpdHMgPSBmdW5jdGlvbihyZXF1aXJlZF9zcGVjaWVzX2xpc3QpIHsKICByZXF1aXJlZF90cmFpdHMgPSB0cmFpdHMgJT4lIGZpbHRlcihqZXR6X3NwZWNpZXNfbmFtZSAlaW4lIHJlcXVpcmVkX3NwZWNpZXNfbGlzdCkKICAKICByZXF1aXJlZF90cmFpdHMkZ2FwZV93aWR0aF9ub3JtYWxpc2VkID0gbm9ybWFsaXNlKHJlcXVpcmVkX3RyYWl0cyRnYXBlX3dpZHRoLCBtaW4ocmVxdWlyZWRfdHJhaXRzJGdhcGVfd2lkdGgpLCBtYXgocmVxdWlyZWRfdHJhaXRzJGdhcGVfd2lkdGgpKQogIHJlcXVpcmVkX3RyYWl0cyRoYW5kd2luZ19pbmRleF9mZGl2X25vcm1hbGlzZWQgPSBub3JtYWxpc2UocmVxdWlyZWRfdHJhaXRzJGhhbmR3aW5nX2luZGV4LCBtaW4ocmVxdWlyZWRfdHJhaXRzJGhhbmR3aW5nX2luZGV4KSwgbWF4KHJlcXVpcmVkX3RyYWl0cyRoYW5kd2luZ19pbmRleCkpCiAgcmVxdWlyZWRfdHJhaXRzJG1hc3Nfbm9ybWFsaXNlZCA9IG5vcm1hbGlzZShyZXF1aXJlZF90cmFpdHMkbWFzcywgbWluKHJlcXVpcmVkX3RyYWl0cyRtYXNzKSwgbWF4KHJlcXVpcmVkX3RyYWl0cyRtYXNzKSkKICAKICB0cmFpdHNfbm9ybWFsaXNlZF9sb25nID0gcmVxdWlyZWRfdHJhaXRzICU+JSBwaXZvdF9sb25nZXIoY29scyA9IGMoJ2dhcGVfd2lkdGhfbm9ybWFsaXNlZCcsICdoYW5kd2luZ19pbmRleF9mZGl2X25vcm1hbGlzZWQnLCAnbWFzc19ub3JtYWxpc2VkJyksIG5hbWVzX3RvID0gJ3RyYWl0JywgdmFsdWVzX3RvID0gJ25vcm1hbGlzZWRfdmFsdWUnKSAlPiUgZHBseXI6OnNlbGVjdChqZXR6X3NwZWNpZXNfbmFtZSwgdHJhaXQsIG5vcm1hbGlzZWRfdmFsdWUpCiAgdHJhaXRzX25vcm1hbGlzZWRfbG9uZyR0cmFpdCA9IGZhY3Rvcih0cmFpdHNfbm9ybWFsaXNlZF9sb25nJHRyYWl0LCBsZXZlbHMgPSBjKCdnYXBlX3dpZHRoX25vcm1hbGlzZWQnLCAnaGFuZHdpbmdfaW5kZXhfZmRpdl9ub3JtYWxpc2VkJywgJ21hc3Nfbm9ybWFsaXNlZCcpLCBsYWJlbHMgPSBjKCdHYXBlIFdpZHRoJywgJ0hXSScsICdNYXNzJykpCiAgCiAgdHJhaXRzX25vcm1hbGlzZWRfbG9uZwp9CgpmZXRjaF9ub3JtYWxpc2VkX3RyYWl0cyhjKCdBcGxvcGVsaWFfbGFydmF0YScsICdDaGFsY29waGFwc19pbmRpY2EnLCAnQ2Fsb2VuYXNfbmljb2JhcmljYScpKQpgYGAKCgpSZWFkIGluIG91ciBwaHlsb2dlbnkKYGBge3J9CnBoeWxvX3RyZWUgPSByZWFkLnRyZWUoZmlsZW5hbWUoVEFYT05PTVlfT1VUUFVUX0RJUiwgJ3BoeWxvZ2VueS50cmUnKSkKZ2d0cmVlKHBoeWxvX3RyZWUsIGxheW91dD0nY2lyY3VsYXInKQpgYGAKCkxvYWQgcmVzb2x2ZSBlY29yZWdpb25zCmBgYHtyfQpyZXNvbHZlID0gcmVhZF9yZXNvbHZlKCkKYGBgCgojIyBDcmVhdGUgaGVscGVyIGZ1bmN0aW9ucwpgYGB7cn0KdG9fc3BlY2llc19tYXRyaXggPSBmdW5jdGlvbihmaWx0ZXJlZF9jb21tdW5pdGllcykgewogIGZpbHRlcmVkX2NvbW11bml0aWVzICU+JSAKICAgIGRwbHlyOjpzZWxlY3QoY2l0eV9pZCwgamV0el9zcGVjaWVzX25hbWUpICU+JSAKICAgIGRpc3RpbmN0KCkgJT4lCiAgICBtdXRhdGUocHJlc2VudCA9IFRSVUUpICU+JSAKICAgIHBpdm90X3dpZGVyKAogICAgICBuYW1lc19mcm9tID0gamV0el9zcGVjaWVzX25hbWUsIAogICAgICB2YWx1ZXNfZnJvbSA9ICJwcmVzZW50IiwgCiAgICAgIHZhbHVlc19maWxsID0gbGlzdChwcmVzZW50ID0gRikKICAgICkgJT4lIAogICAgdGliYmxlOjpjb2x1bW5fdG9fcm93bmFtZXModmFyPSdjaXR5X2lkJykKfQpgYGAKCmBgYHtyfQpjb21tdW5pdHlfbm1kcyA9IGZ1bmN0aW9uKGZpbHRlcmVkX2NvbW11bml0aWVzKSB7CiAgc3BlY2llc19tYXRyaXggPSB0b19zcGVjaWVzX21hdHJpeChmaWx0ZXJlZF9jb21tdW5pdGllcykKICBubWRzIDwtIG1ldGFNRFMoc3BlY2llc19tYXRyaXgsIGs9MiwgdHJ5bWF4ID0gMzApCiAgbm1kc19yZXN1bHQgPSBkYXRhLmZyYW1lKHZlZ2FuOjpzY29yZXMobm1kcykkc2l0ZXMpCiAgbm1kc19yZXN1bHQkY2l0eV9pZCA9IGFzLmRvdWJsZShyb3duYW1lcyhubWRzX3Jlc3VsdCkpCiAgcm93bmFtZXMobm1kc19yZXN1bHQpID0gTlVMTAogIG5tZHNfcmVzdWx0Cn0KYGBgCgpodHRwczovL3d3dy5kYXRhY2FtcC5jb20vdHV0b3JpYWwvay1tZWFucy1jbHVzdGVyaW5nLXIKYGBge3J9CnNjcmVlX3Bsb3QgPSBmdW5jdGlvbihjb21tdW5pdHlfbm1kc19kYXRhKSB7CiAgIyBEZWNpZGUgaG93IG1hbnkgY2x1c3RlcnMgdG8gbG9vayBhdAogIG5fY2x1c3RlcnMgPC0gbWluKDEwLCBucm93KGNvbW11bml0eV9ubWRzX2RhdGEpIC0gMSkKICAKICAjIEluaXRpYWxpemUgdG90YWwgd2l0aGluIHN1bSBvZiBzcXVhcmVzIGVycm9yOiB3c3MKICB3c3MgPC0gbnVtZXJpYyhuX2NsdXN0ZXJzKQogIAogIHNldC5zZWVkKDEyMykKICAKICAjIExvb2sgb3ZlciAxIHRvIG4gcG9zc2libGUgY2x1c3RlcnMKICBmb3IgKGkgaW4gMTpuX2NsdXN0ZXJzKSB7CiAgICAjIEZpdCB0aGUgbW9kZWw6IGttLm91dAogICAga20ub3V0IDwtIGttZWFucyhjb21tdW5pdHlfbm1kc19kYXRhWyxjKCdOTURTMScsJ05NRFMyJyldLCBjZW50ZXJzID0gaSwgbnN0YXJ0ID0gMjApCiAgICAjIFNhdmUgdGhlIHdpdGhpbiBjbHVzdGVyIHN1bSBvZiBzcXVhcmVzCiAgICB3c3NbaV0gPC0ga20ub3V0JHRvdC53aXRoaW5zcwogIH0KICAKICAjIFByb2R1Y2UgYSBzY3JlZSBwbG90CiAgd3NzX2RmIDwtIHRpYmJsZShjbHVzdGVycyA9IDE6bl9jbHVzdGVycywgd3NzID0gd3NzKQogICAKICBzY3JlZV9wbG90IDwtIGdncGxvdCh3c3NfZGYsIGFlcyh4ID0gY2x1c3RlcnMsIHkgPSB3c3MsIGdyb3VwID0gMSkpICsKICAgICAgZ2VvbV9wb2ludChzaXplID0gNCkgKwogICAgICBnZW9tX2xpbmUoKSArCiAgICAgIGdlb21faGxpbmUobGluZXR5cGU9ImRhc2hlZCIsIGNvbG9yID0gIm9yYW5nZSIsIHlpbnRlcmNlcHQgPSB3c3MpICsKICAgICAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IGMoMiwgNCwgNiwgOCwgMTApKSArCiAgICAgIHhsYWIoJ051bWJlciBvZiBjbHVzdGVycycpCiAgc2NyZWVfcGxvdAp9CmBgYAoKYGBge3J9CmNsdXN0ZXJfY2l0aWVzID0gZnVuY3Rpb24oY2l0eV9ubWRzLCBjaXRpZXNfY29tbXVuaXR5X2RhdGEsIGNlbnRlcnMpIHsKICBzZXQuc2VlZCgxMjMpCiAga21lYW5zX2NsdXN0ZXJzIDwtIGttZWFucyhjaXR5X25tZHNbLGMoJ05NRFMxJywgJ05NRFMyJyldLCBjZW50ZXJzID0gY2VudGVycywgbnN0YXJ0ID0gMjApCiAgY2l0eV9ubWRzJGNsdXN0ZXIgPSBrbWVhbnNfY2x1c3RlcnMkY2x1c3RlcgogIGNpdGllc19jb21tdW5pdHlfZGF0YSAlPiUgbGVmdF9qb2luKGNpdHlfbm1kcykgJT4lIG11dGF0ZShjbHVzdGVyID0gYXMuZmFjdG9yKGNsdXN0ZXIpKQp9CmBgYAoKYGBge3J9CnBsb3Rfbm1kc19jbHVzdGVycyA9IGZ1bmN0aW9uKGNsdXN0ZXJfY2l0aWVzKSB7CiAgY2x1c3Rlcl9jaXRpZXMgJT4lIGRwbHlyOjpzZWxlY3QoY2l0eV9pZCwgY2l0eV9uYW1lLCBOTURTMSwgTk1EUzIsIGNsdXN0ZXIpICU+JSBkaXN0aW5jdCgpICU+JQogIGdncGxvdChhZXMoeCA9IE5NRFMxLCB5ID0gTk1EUzIsIGNvbG91ciA9IGNsdXN0ZXIpKSArIGdlb21fcG9pbnQoKSArIGdlb21fbGFiZWxfcmVwZWwoYWVzKGxhYmVsID0gY2l0eV9uYW1lKSkKfQpgYGAKCmBgYHtyfQpnZXRfcHJlc2VuY2VfY2VsbF93aWR0aCA9IGZ1bmN0aW9uKGNpdHlfY2x1c3Rlcl9kYXRhX21ldHJpY3MpIHsKICAxMCAqIGxlbmd0aCh1bmlxdWUoY2l0eV9jbHVzdGVyX2RhdGFfbWV0cmljcyRjaXR5X2lkKSkKfQoKZ2V0X3ByZXNlbmNlX2NlbGxfaGVpZ2h0ID0gZnVuY3Rpb24oY2l0eV9jbHVzdGVyX2RhdGFfbWV0cmljcykgewogIHNwZWNpZXMgPSBzcGVjaWVzX2luX2NsdXN0ZXIgPSBjb21tdW5pdGllcyAlPiUgCiAgICBmaWx0ZXIoY2l0eV9pZCAlaW4lIGNpdHlfY2x1c3Rlcl9kYXRhX21ldHJpY3MkY2l0eV9pZCkgJT4lIAogICAgZHBseXI6OnNlbGVjdChqZXR6X3NwZWNpZXNfbmFtZSkgJT4lIAogICAgZGlzdGluY3QoKQogIAogIDEwICogbnJvdyhzcGVjaWVzKQp9CgpjaXR5X21ldHJpY19oZWlnaHQgPSAzMAp0cmFpdHNfd2lkdGggPSA1MApwaHlsb190cmVlX3dpZHRoID0gMTI1CnRpdGxlX2hlaWdodCA9IDgKCmdldF9pbWFnZV9oZWlnaHQgPSBmdW5jdGlvbihjaXR5X2NsdXN0ZXJfZGF0YV9tZXRyaWNzKSB7CiAgZ2V0X3ByZXNlbmNlX2NlbGxfaGVpZ2h0KGNpdHlfY2x1c3Rlcl9kYXRhX21ldHJpY3MpICsgKDMgKiBjaXR5X21ldHJpY19oZWlnaHQpICsgdGl0bGVfaGVpZ2h0Cn0KCmdldF9pbWFnZV93aWR0aCA9IGZ1bmN0aW9uKGNpdHlfY2x1c3Rlcl9kYXRhX21ldHJpY3MpIHsKICBnZXRfcHJlc2VuY2VfY2VsbF93aWR0aChjaXR5X2NsdXN0ZXJfZGF0YV9tZXRyaWNzKSArIHRyYWl0c193aWR0aCArIHBoeWxvX3RyZWVfd2lkdGgKfQpgYGAKCmBgYHtyfQpzcGVjaWVzX2luX2NpdHlfbGFiZWwgPSBmdW5jdGlvbihzcGVjaWVzKSB7CiAgcGFzdGUoCiAgICBpZmVsc2Uoc3BlY2llcyRzZWFzb25hbCA9PSAnUmVzaWRlbnQnLCAnJywgc3Vic3RyaW5nKHNwZWNpZXMkc2Vhc29uYWwsIDAsIDEpKSwKICAgIGlmZWxzZShzcGVjaWVzJG9yaWdpbiA9PSAnTmF0aXZlJywgJycsIHN1YnN0cmluZyhzcGVjaWVzJG9yaWdpbiwgMCwgMSkpLAogICAgaWZlbHNlKHNwZWNpZXMkZGlzdGFuY2VfdG9fbm9ydGhlcm5fZWRnZV9rbSA+IDIwMCwgJycsIHBhc3RlKCdOUkwnLCByb3VuZChzcGVjaWVzJGRpc3RhbmNlX3RvX25vcnRoZXJuX2VkZ2Vfa20pLCBzZXAgPSAnICcpKSwKICAgIGlmZWxzZShzcGVjaWVzJGRpc3RhbmNlX3RvX3NvdXRoZXJuX2VkZ2Vfa20gPiAyMDAsICcnLCBwYXN0ZSgnU1JMJywgcm91bmQoc3BlY2llcyRkaXN0YW5jZV90b19ub3J0aGVybl9lZGdlX2ttKSwgc2VwID0gJyAnKSksCiAgICBzZXAgPSAnXG4nCiAgKQp9CgpzcGVjaWVzX2luX2NpdHlfbGFiZWwoaGVhZChjb21tdW5pdGllcykpCmBgYAoKYGBge3J9CnBsb3RfY2l0eV9jbHVzdGVyID0gZnVuY3Rpb24oY2l0eV9jbHVzdGVyX2RhdGFfbWV0cmljcywgdGl0bGUpIHsKICBzcGVjaWVzX2luX2NsdXN0ZXIgPSBjb21tdW5pdGllcyAlPiUgCiAgICBmaWx0ZXIoY2l0eV9pZCAlaW4lIGNpdHlfY2x1c3Rlcl9kYXRhX21ldHJpY3MkY2l0eV9pZCkgJT4lIAogICAgZHBseXI6OnNlbGVjdChqZXR6X3NwZWNpZXNfbmFtZSwgY2l0eV9uYW1lLCByZWxhdGl2ZV9hYnVuZGFuY2VfcHJveHksIHNlYXNvbmFsLCBvcmlnaW4sIGRpc3RhbmNlX3RvX25vcnRoZXJuX2VkZ2Vfa20sIGRpc3RhbmNlX3RvX3NvdXRoZXJuX2VkZ2Vfa20pCiAgCiAgc3BlY2llc19pbl9jbHVzdGVyJGxhYmVsID0gc3BlY2llc19pbl9jaXR5X2xhYmVsKHNwZWNpZXNfaW5fY2x1c3RlcikKICB0cmVlX2Nyb3BwZWQgPC0gbGFkZGVyaXplKGRyb3AudGlwKHBoeWxvX3RyZWUsIHNldGRpZmYocGh5bG9fdHJlZSR0aXAubGFiZWwsIHNwZWNpZXNfaW5fY2x1c3RlciRqZXR6X3NwZWNpZXNfbmFtZSkpKQogICAgCiAgZ2dfdHJlZSA9IGdndHJlZSh0cmVlX2Nyb3BwZWQpCiAgCiAgZ2dfcHJlc2VuY2UgPSBnZ3Bsb3Qoc3BlY2llc19pbl9jbHVzdGVyLCBhZXMoeD1jaXR5X25hbWUsIHk9amV0el9zcGVjaWVzX25hbWUpKSArIAogICAgICAgICAgZ2VvbV90aWxlKGFlcyhmaWxsPXJlbGF0aXZlX2FidW5kYW5jZV9wcm94eSkpICsgCiAgICAgICAgICBnZW9tX3RleHQoYWVzKGxhYmVsPWxhYmVsKSwgc2l6ZT0wLjc1KSArCiAgICAgICAgICBzY2FsZV9maWxsX2dyYWRpZW50bihjb2xvdXJzPWMoIiM5OEZCOTgiLCAiI0ZGRkZFMCIsICJ5ZWxsb3ciLCAib3JhbmdlIiwgIiNGRjQ1MDAiLCAicmVkIiwgInJlZCIpLCB2YWx1ZXM9YygwLCAwLjAwMDAwMDAwMDAxLCAwLjEsIDAuMjUsIDAuNSwgMC43NSwgMSksIG5hLnZhbHVlID0gInRyYW5zcGFyZW50IikgKwogICAgICAgICAgdGhlbWVfbWluaW1hbCgpICsgeGxhYihOVUxMKSArIHlsYWIoTlVMTCkgKyAKICAgICAgICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIHZqdXN0ID0gMC41LCBoanVzdD0xKSkgKyAKICAgICAgICAgIGxhYnMoZmlsbD0nVXJiYW4gUHJveHkgQWJ1bmRhbmNlJykKICAKICBzcGVjaWVzX2luX2NsdXN0ZXJfdHJhaXRzID0gZmV0Y2hfbm9ybWFsaXNlZF90cmFpdHMoc3BlY2llc19pbl9jbHVzdGVyJGpldHpfc3BlY2llc19uYW1lKQogIAogIGdnX3RyYWl0cyA9IGdncGxvdChzcGVjaWVzX2luX2NsdXN0ZXJfdHJhaXRzLCBhZXMoeCA9IHRyYWl0LCB5ID0gamV0el9zcGVjaWVzX25hbWUsIHNpemUgPSBub3JtYWxpc2VkX3ZhbHVlKSkgKyBnZW9tX3BvaW50KCkgKyB0aGVtZV9taW5pbWFsKCkgKyB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCB2anVzdCA9IDAuNSwgaGp1c3Q9MSksIGF4aXMudGV4dC55PWVsZW1lbnRfYmxhbmsoKSkgKyB4bGFiKE5VTEwpICsgeWxhYihOVUxMKSArIGxhYnMoc2l6ZSA9ICJOb3JtYWxpc2VkIFZhbHVlIikKICAKICBnZ19jaXRpZXNfbW50ZCA9IGdncGxvdChjaXR5X2NsdXN0ZXJfZGF0YV9tZXRyaWNzLCBhZXMoeCA9IGNpdHlfbmFtZSwgeSA9IG1udGRfbm9ybWFsaXNlZCkpICsgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpICsgdGhlbWVfbWluaW1hbCgpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLCBheGlzLnRleHQueD1lbGVtZW50X2JsYW5rKCkpICsgeGxhYihOVUxMKSArIHlsYWIoIk1OVEQiKSArIHlsaW0oMCwgMSkKICAKICBnZ19jaXRpZXNfZ2FwZV9mZCA9IGdncGxvdChjaXR5X2NsdXN0ZXJfZGF0YV9tZXRyaWNzLCBhZXMoeCA9IGNpdHlfbmFtZSwgeSA9IGdhcGVfd2lkdGhfZmRpdl9ub3JtYWxpc2VkKSkgKyBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IikgKyB0aGVtZV9taW5pbWFsKCkgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsIGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSkgKyB4bGFiKE5VTEwpICsgeWxhYigiR2FwZSIpICsgeWxpbSgwLCAxKQogIAogIGdnX2NpdGllc19sb2NvX2ZkID0gZ2dwbG90KGNpdHlfY2x1c3Rlcl9kYXRhX21ldHJpY3MsIGFlcyh4ID0gY2l0eV9uYW1lLCB5ID0gaGFuZHdpbmdfaW5kZXhfZmRpdl9ub3JtYWxpc2VkKSkgKyBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IikgKyB0aGVtZV9taW5pbWFsKCkgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsIGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSkgKyB4bGFiKE5VTEwpICsgeWxhYigiSFdJIikgKyB5bGltKDAsIDEpCiAgCiAgZ2dfdGl0bGUgPSBnZ3Bsb3QoKSArIGxhYnModGl0bGUgPSB0aXRsZSwgc3VidGl0bGUgPSBwYXN0ZSgKICAgIHRlc3RfdmFsdWUoJ01OVEQnLCBjaXR5X2NsdXN0ZXJfZGF0YV9tZXRyaWNzJG1udGRfbm9ybWFsaXNlZCksCiAgICB0ZXN0X3ZhbHVlKCdDb21wb3VuZCBGRGl2JywgY2l0eV9jbHVzdGVyX2RhdGFfbWV0cmljcyRmZGl2X25vcm1hbGlzZWQpLAogICAgdGVzdF92YWx1ZSgnSFdJIEZEaXYnLCBjaXR5X2NsdXN0ZXJfZGF0YV9tZXRyaWNzJGhhbmR3aW5nX2luZGV4X2ZkaXZfbm9ybWFsaXNlZCksCiAgICB0ZXN0X3ZhbHVlKCdHYXBlIHdpZHRoIEZEaXYnLCBjaXR5X2NsdXN0ZXJfZGF0YV9tZXRyaWNzJGdhcGVfd2lkdGhfZmRpdl9ub3JtYWxpc2VkKSwKICAgIHNlcCA9ICdcbicKICApKSArIHRoZW1lX21pbmltYWwoKSArIHRoZW1lKHBsb3Quc3VidGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9OCwgaGp1c3Q9MCwgY29sb3I9IiM0NDQ0NDQiKSkKICAKICBnZ19wcmVzZW5jZV9oZWlnaHQgPSBnZXRfcHJlc2VuY2VfY2VsbF9oZWlnaHQoY2l0eV9jbHVzdGVyX2RhdGFfbWV0cmljcykKICBnZ19wcmVzZW5jZV93aWR0aCA9IGdldF9wcmVzZW5jZV9jZWxsX3dpZHRoKGNpdHlfY2x1c3Rlcl9kYXRhX21ldHJpY3MpCiAgCiAgZ2dfcHJlc2VuY2UgJT4lIGluc2VydF90b3AoZ2dfY2l0aWVzX2xvY29fZmQsIGhlaWdodCA9IChjaXR5X21ldHJpY19oZWlnaHQgLyBnZ19wcmVzZW5jZV9oZWlnaHQpKSAlPiUgaW5zZXJ0X3RvcChnZ19jaXRpZXNfZ2FwZV9mZCwgaGVpZ2h0ID0gKGNpdHlfbWV0cmljX2hlaWdodCAvIGdnX3ByZXNlbmNlX2hlaWdodCkpICU+JSBpbnNlcnRfdG9wKGdnX2NpdGllc19tbnRkLCBoZWlnaHQgPSAoY2l0eV9tZXRyaWNfaGVpZ2h0IC8gZ2dfcHJlc2VuY2VfaGVpZ2h0KSkgJT4lIGluc2VydF9sZWZ0KGdnX3RyZWUsIHdpZHRoID0gKHBoeWxvX3RyZWVfd2lkdGggLyBnZ19wcmVzZW5jZV93aWR0aCkpICU+JSBpbnNlcnRfcmlnaHQoZ2dfdHJhaXRzLCB3aWR0aCA9ICh0cmFpdHNfd2lkdGggLyBnZ19wcmVzZW5jZV93aWR0aCkpICU+JSBpbnNlcnRfdG9wKGdnX3RpdGxlLCBoZWlnaHQgPSAodGl0bGVfaGVpZ2h0IC8gZ2dfcHJlc2VuY2VfaGVpZ2h0KSkKfQpgYGAKCmBgYHtyfQpSRUdJT05fREVFUF9ESVZFX0ZJR1VSRVNfT1VUUFVUID0gbWtkaXIoRklHVVJFU19PVVRQVVRfRElSLCAnYXBwZW5kaXhfcmVnaW9uYWxfZGVlcF9kaXZlX3VzaW5nX2FidW5kYW5jZScpCmBgYAoKIyMgTmVhcmN0aWMKYGBge3J9Cm5lYXJjdGljX2NpdGllc19jb21tdW5pdHlfZGF0YSA9IGNvbW11bml0eV9kYXRhX21ldHJpY3MgJT4lIGZpbHRlcihjb3JlX3JlYWxtID09ICdOZWFyY3RpYycpCm5lYXJjdGljX2NpdGllc19jb21tdW5pdHlfZGF0YSAlPiUgZHBseXI6OnNlbGVjdChjaXR5X25hbWUpICU+JSBkaXN0aW5jdCgpICU+JSBhcy5saXN0KCkKYGBgCgpgYGB7cn0KbmVhcmN0aWNfY2l0aWVzX25tZHMgPSBjb21tdW5pdHlfbm1kcyhjb21tdW5pdGllcyAlPiUgZmlsdGVyKGNpdHlfaWQgJWluJSBuZWFyY3RpY19jaXRpZXNfY29tbXVuaXR5X2RhdGEkY2l0eV9pZCkpIApuZWFyY3RpY19jaXRpZXNfbm1kcwpgYGAKCmBgYHtyfQpzY3JlZV9wbG90KG5lYXJjdGljX2NpdGllc19ubWRzKQpgYGAKCmBgYHtyfQpuZWFyY3RpY19jaXRpZXMgPSBjbHVzdGVyX2NpdGllcyhjaXR5X25tZHMgPSBuZWFyY3RpY19jaXRpZXNfbm1kcywgY2l0aWVzX2NvbW11bml0eV9kYXRhID0gbmVhcmN0aWNfY2l0aWVzX2NvbW11bml0eV9kYXRhLCBjZW50ZXJzID0gNCkKYGBgCgpgYGB7cn0KcGxvdF9ubWRzX2NsdXN0ZXJzKG5lYXJjdGljX2NpdGllcykKYGBgCgpgYGB7cn0KbmVhcmN0aWNfYmlvbWVzID0gc3RfY3JvcChyZXNvbHZlW3Jlc29sdmUkUkVBTE0gPT0gJ05lYXJjdGljJyxjKCdSRUFMTScpXSwgeG1pbiA9IC0yMjAsIHltaW4gPSAwLCB4bWF4ID0gMCwgeW1heCA9IDcwKQogCmdncGxvdCgpICsgCiAgZ2VvbV9zZihkYXRhID0gbmVhcmN0aWNfYmlvbWVzLCBhZXMoZ2VvbWV0cnkgPSBnZW9tZXRyeSkpICsgCiAgZ2VvbV9zZihkYXRhID0gbmVhcmN0aWNfY2l0aWVzLCBhZXMoZ2VvbWV0cnkgPSBnZW9tZXRyeSwgY29sb3IgPSBjbHVzdGVyKSkKCmdnc2F2ZShmaWxlbmFtZShSRUdJT05fREVFUF9ESVZFX0ZJR1VSRVNfT1VUUFVULCAnbmVhcnRpY19jbHVzdGVycy5qcGcnKSkKYGBgCmBgYHtyfQp0ZXN0X3ZhbHVlKCdOZWFyY3RpYyBNTlREJywgbmVhcmN0aWNfY2l0aWVzJG1udGRfbm9ybWFsaXNlZCkKdGVzdF92YWx1ZSgnTmVhcmN0aWMgYmVhayBnYXBlIEZEaXYnLCBuZWFyY3RpY19jaXRpZXMkZ2FwZV93aWR0aF9mZGl2X25vcm1hbGlzZWQpCnRlc3RfdmFsdWUoJ05lYXJjdGljIEhXSSBGRGl2JywgbmVhcmN0aWNfY2l0aWVzJGhhbmR3aW5nX2luZGV4X2ZkaXZfbm9ybWFsaXNlZCkKbnJvdyhuZWFyY3RpY19jaXRpZXMpCmBgYAoKCiMjIyBOZWFydGljIENsdXN0ZXIgMWAKYGBge3J9Cm5lYXJhY3RpY19jbHVzdGVyMSA9IG5lYXJjdGljX2NpdGllcyAlPiUgZmlsdGVyKGNsdXN0ZXIgPT0gMSkKcGxvdF9jaXR5X2NsdXN0ZXIobmVhcmFjdGljX2NsdXN0ZXIxLCAnTmVhcnRpYyBjbHVzdGVyIDEnKQpnZ3NhdmUoZmlsZW5hbWUoUkVHSU9OX0RFRVBfRElWRV9GSUdVUkVTX09VVFBVVCwgJ25lYXJ0aWNfY2x1c3RlcjEuanBnJyksIHdpZHRoID0gZ2V0X2ltYWdlX3dpZHRoKG5lYXJhY3RpY19jbHVzdGVyMSksIGhlaWdodCA9IGdldF9pbWFnZV9oZWlnaHQobmVhcmFjdGljX2NsdXN0ZXIxKSwgdW5pdHMgPSAibW0iKQpgYGAKCiMjIyBOZWFydGljIENsdXN0ZXIgMgpgYGB7cn0KbmVhcmFjdGljX2NsdXN0ZXIyID0gbmVhcmN0aWNfY2l0aWVzICU+JSBmaWx0ZXIoY2x1c3RlciA9PSAyKQpwbG90X2NpdHlfY2x1c3RlcihuZWFyYWN0aWNfY2x1c3RlcjIsICdOZWFydGljIGNsdXN0ZXIgMicpCmdnc2F2ZShmaWxlbmFtZShSRUdJT05fREVFUF9ESVZFX0ZJR1VSRVNfT1VUUFVULCAnbmVhcnRpY19jbHVzdGVyMi5qcGcnKSwgd2lkdGggPSBnZXRfaW1hZ2Vfd2lkdGgobmVhcmFjdGljX2NsdXN0ZXIyKSwgaGVpZ2h0ID0gZ2V0X2ltYWdlX2hlaWdodChuZWFyYWN0aWNfY2x1c3RlcjIpLCB1bml0cyA9ICJtbSIpCmBgYAoKIyMjIE5lYXJ0aWMgQ2x1c3RlciAzCmBgYHtyfQpuZWFyYWN0aWNfY2x1c3RlcjMgPSBuZWFyY3RpY19jaXRpZXMgJT4lIGZpbHRlcihjbHVzdGVyID09IDMpCnBsb3RfY2l0eV9jbHVzdGVyKG5lYXJhY3RpY19jbHVzdGVyMywgJ05lYXJ0aWMgY2x1c3RlciAzJykKZ2dzYXZlKGZpbGVuYW1lKFJFR0lPTl9ERUVQX0RJVkVfRklHVVJFU19PVVRQVVQsICduZWFydGljX2NsdXN0ZXIzLmpwZycpLCB3aWR0aCA9IGdldF9pbWFnZV93aWR0aChuZWFyYWN0aWNfY2x1c3RlcjMpLCBoZWlnaHQgPSBnZXRfaW1hZ2VfaGVpZ2h0KG5lYXJhY3RpY19jbHVzdGVyMyksIHVuaXRzID0gIm1tIikKYGBgCgojIyMgTmVhcnRpYyBDbHVzdGVyIDQKYGBge3J9Cm5lYXJhY3RpY19jbHVzdGVyNCA9IG5lYXJjdGljX2NpdGllcyAlPiUgZmlsdGVyKGNsdXN0ZXIgPT0gNCkKcGxvdF9jaXR5X2NsdXN0ZXIobmVhcmFjdGljX2NsdXN0ZXI0LCAnTmVhcnRpYyBjbHVzdGVyIDQnKQpnZ3NhdmUoZmlsZW5hbWUoUkVHSU9OX0RFRVBfRElWRV9GSUdVUkVTX09VVFBVVCwgJ25lYXJ0aWNfY2x1c3RlcjQuanBnJyksIHdpZHRoID0gZ2V0X2ltYWdlX3dpZHRoKG5lYXJhY3RpY19jbHVzdGVyNCksIGhlaWdodCA9IGdldF9pbWFnZV9oZWlnaHQobmVhcmFjdGljX2NsdXN0ZXI0KSwgdW5pdHMgPSAibW0iKQpgYGAKCiMjIE5lb3Ryb3BpYwpgYGB7cn0KbmVvdHJvcGljX2NpdGllc19jb21tdW5pdHlfZGF0YSA9IGNvbW11bml0eV9kYXRhX21ldHJpY3MgJT4lIGZpbHRlcihjb3JlX3JlYWxtID09ICdOZW90cm9waWMnKQpuZW90cm9waWNfY2l0aWVzX2NvbW11bml0eV9kYXRhICU+JSBkcGx5cjo6c2VsZWN0KGNpdHlfbmFtZSkgJT4lIGRpc3RpbmN0KCkgJT4lIGFzLmxpc3QoKQpgYGAKCmBgYHtyfQpuZW90cm9waWNfY2l0aWVzX25tZHMgPSBjb21tdW5pdHlfbm1kcyhjb21tdW5pdGllcyAlPiUgZmlsdGVyKGNpdHlfaWQgJWluJSBuZW90cm9waWNfY2l0aWVzX2NvbW11bml0eV9kYXRhJGNpdHlfaWQpKSAKbmVvdHJvcGljX2NpdGllc19ubWRzCmBgYAoKYGBge3J9CnNjcmVlX3Bsb3QobmVvdHJvcGljX2NpdGllc19ubWRzKQpgYGAKCmBgYHtyfQpuZW90cm9waWNfY2l0aWVzID0gY2x1c3Rlcl9jaXRpZXMoY2l0eV9ubWRzID0gbmVvdHJvcGljX2NpdGllc19ubWRzLCBjaXRpZXNfY29tbXVuaXR5X2RhdGEgPSBuZW90cm9waWNfY2l0aWVzX2NvbW11bml0eV9kYXRhLCBjZW50ZXJzID0gNSkKYGBgCgpgYGB7cn0KcGxvdF9ubWRzX2NsdXN0ZXJzKG5lb3Ryb3BpY19jaXRpZXMpCmBgYAoKYGBge3J9Cm5lb3Ryb3BpY19iaW9tZXMgPSByZXNvbHZlW3Jlc29sdmUkUkVBTE0gPT0gJ05lb3Ryb3BpYycsYygnUkVBTE0nKV0KIApnZ3Bsb3QoKSArIAogIGdlb21fc2YoZGF0YSA9IG5lb3Ryb3BpY19iaW9tZXMsIGFlcyhnZW9tZXRyeSA9IGdlb21ldHJ5KSkgKyAKICBnZW9tX3NmKGRhdGEgPSBuZW90cm9waWNfY2l0aWVzLCBhZXMoZ2VvbWV0cnkgPSBnZW9tZXRyeSwgY29sb3IgPSBjbHVzdGVyKSkKZ2dzYXZlKGZpbGVuYW1lKFJFR0lPTl9ERUVQX0RJVkVfRklHVVJFU19PVVRQVVQsICduZW90cm9waWNfY2x1c3RlcnMuanBnJykpCmBgYAoKYGBge3J9CnRlc3RfdmFsdWUoJ05lb3Ryb3BpYyBNTlREJywgbmVvdHJvcGljX2NpdGllcyRtbnRkX25vcm1hbGlzZWQpCnRlc3RfdmFsdWUoJ05lb3Ryb3BpYyBiZWFrIGdhcGUgRkRpdicsIG5lb3Ryb3BpY19jaXRpZXMkZ2FwZV93aWR0aF9mZGl2X25vcm1hbGlzZWQpCnRlc3RfdmFsdWUoJ05lb3Ryb3BpYyBIV0kgRkRpdicsIG5lb3Ryb3BpY19jaXRpZXMkaGFuZHdpbmdfaW5kZXhfZmRpdl9ub3JtYWxpc2VkKQpucm93KG5lb3Ryb3BpY19jaXRpZXMpCmBgYAoKIyMjIE5lb3Ryb3BpYyBDbHVzdGVyIDEKYGBge3J9Cm5lb3Ryb3BpY19jbHVzdGVyMSA9IG5lb3Ryb3BpY19jaXRpZXMgJT4lIGZpbHRlcihjbHVzdGVyID09IDEpCnBsb3RfY2l0eV9jbHVzdGVyKG5lb3Ryb3BpY19jbHVzdGVyMSwgJ05lb3Ryb3BpYyBjbHVzdGVyIDEnKQpnZ3NhdmUoZmlsZW5hbWUoUkVHSU9OX0RFRVBfRElWRV9GSUdVUkVTX09VVFBVVCwgJ25lb3Ryb3BpY19jbHVzdGVyMS5qcGcnKSwgd2lkdGggPSBnZXRfaW1hZ2Vfd2lkdGgobmVvdHJvcGljX2NsdXN0ZXIxKSwgaGVpZ2h0ID0gZ2V0X2ltYWdlX2hlaWdodChuZW90cm9waWNfY2x1c3RlcjEpLCB1bml0cyA9ICJtbSIpCmBgYAoKIyMjIE5lb3Ryb3BpYyBDbHVzdGVyIDIKYGBge3J9Cm5lb3Ryb3BpY19jbHVzdGVyMiA9IG5lb3Ryb3BpY19jaXRpZXMgJT4lIGZpbHRlcihjbHVzdGVyID09IDIpCnBsb3RfY2l0eV9jbHVzdGVyKG5lb3Ryb3BpY19jbHVzdGVyMiwgJ05lb3Ryb3BpYyBjbHVzdGVyIDInKQpnZ3NhdmUoZmlsZW5hbWUoUkVHSU9OX0RFRVBfRElWRV9GSUdVUkVTX09VVFBVVCwgJ25lb3Ryb3BpY19jbHVzdGVyMi5qcGcnKSwgd2lkdGggPSBnZXRfaW1hZ2Vfd2lkdGgobmVvdHJvcGljX2NsdXN0ZXIyKSwgaGVpZ2h0ID0gZ2V0X2ltYWdlX2hlaWdodChuZW90cm9waWNfY2x1c3RlcjIpLCB1bml0cyA9ICJtbSIpCmBgYAoKIyMjIE5lb3Ryb3BpYyBDbHVzdGVyIDMKYGBge3J9Cm5lb3Ryb3BpY19jbHVzdGVyMyA9IG5lb3Ryb3BpY19jaXRpZXMgJT4lIGZpbHRlcihjbHVzdGVyID09IDMpCnBsb3RfY2l0eV9jbHVzdGVyKG5lb3Ryb3BpY19jbHVzdGVyMywgJ05lb3Ryb3BpYyBjbHVzdGVyIDMnKQpnZ3NhdmUoZmlsZW5hbWUoUkVHSU9OX0RFRVBfRElWRV9GSUdVUkVTX09VVFBVVCwgJ25lb3Ryb3BpY19jbHVzdGVyMy5qcGcnKSwgd2lkdGggPSBnZXRfaW1hZ2Vfd2lkdGgobmVvdHJvcGljX2NsdXN0ZXIzKSwgaGVpZ2h0ID0gZ2V0X2ltYWdlX2hlaWdodChuZW90cm9waWNfY2x1c3RlcjMpLCB1bml0cyA9ICJtbSIpCmBgYAoKIyMjIE5lb3Ryb3BpYyBDbHVzdGVyIDQKYGBge3J9Cm5lb3Ryb3BpY19jbHVzdGVyNCA9IG5lb3Ryb3BpY19jaXRpZXMgJT4lIGZpbHRlcihjbHVzdGVyID09IDQpCnBsb3RfY2l0eV9jbHVzdGVyKG5lb3Ryb3BpY19jbHVzdGVyNCwgJ05lb3Ryb3BpYyBjbHVzdGVyIDQnKQpnZ3NhdmUoZmlsZW5hbWUoUkVHSU9OX0RFRVBfRElWRV9GSUdVUkVTX09VVFBVVCwgJ25lb3Ryb3BpY19jbHVzdGVyNC5qcGcnKSwgd2lkdGggPSBnZXRfaW1hZ2Vfd2lkdGgobmVvdHJvcGljX2NsdXN0ZXI0KSwgaGVpZ2h0ID0gZ2V0X2ltYWdlX2hlaWdodChuZW90cm9waWNfY2x1c3RlcjQpLCB1bml0cyA9ICJtbSIpCmBgYAoKIyMjIE5lb3Ryb3BpYyBDbHVzdGVyIDUKYGBge3J9Cm5lb3Ryb3BpY19jbHVzdGVyNSA9IG5lb3Ryb3BpY19jaXRpZXMgJT4lIGZpbHRlcihjbHVzdGVyID09IDUpCnBsb3RfY2l0eV9jbHVzdGVyKG5lb3Ryb3BpY19jbHVzdGVyNSwgJ05lb3Ryb3BpYyBjbHVzdGVyIDUnKQpnZ3NhdmUoZmlsZW5hbWUoUkVHSU9OX0RFRVBfRElWRV9GSUdVUkVTX09VVFBVVCwgJ25lb3Ryb3BpY19jbHVzdGVyNS5qcGcnKSwgd2lkdGggPSBnZXRfaW1hZ2Vfd2lkdGgobmVvdHJvcGljX2NsdXN0ZXI1KSwgaGVpZ2h0ID0gZ2V0X2ltYWdlX2hlaWdodChuZW90cm9waWNfY2x1c3RlcjUpLCB1bml0cyA9ICJtbSIpCmBgYAoKIyMgUGFsZWFyY3RpYwpgYGB7cn0KcGFsZWFyY3RpY19jaXRpZXNfY29tbXVuaXR5X2RhdGEgPSBjb21tdW5pdHlfZGF0YV9tZXRyaWNzICU+JSBmaWx0ZXIoY29yZV9yZWFsbSA9PSAnUGFsZWFyY3RpYycpCnBhbGVhcmN0aWNfY2l0aWVzX2NvbW11bml0eV9kYXRhICU+JSBkcGx5cjo6c2VsZWN0KGNpdHlfbmFtZSkgJT4lIGRpc3RpbmN0KCkgJT4lIGFzLmxpc3QoKQpgYGAKCmBgYHtyfQpwYWxlYXJjdGljX2NpdGllc19ubWRzID0gY29tbXVuaXR5X25tZHMoY29tbXVuaXRpZXMgJT4lIGZpbHRlcihjaXR5X2lkICVpbiUgcGFsZWFyY3RpY19jaXRpZXNfY29tbXVuaXR5X2RhdGEkY2l0eV9pZCkpIApwYWxlYXJjdGljX2NpdGllc19ubWRzCmBgYAoKYGBge3J9CnNjcmVlX3Bsb3QocGFsZWFyY3RpY19jaXRpZXNfbm1kcykKYGBgCgpgYGB7cn0KcGFsZWFyY3RpY19jaXRpZXMgPSBjbHVzdGVyX2NpdGllcyhjaXR5X25tZHMgPSBwYWxlYXJjdGljX2NpdGllc19ubWRzLCBjaXRpZXNfY29tbXVuaXR5X2RhdGEgPSBwYWxlYXJjdGljX2NpdGllc19jb21tdW5pdHlfZGF0YSwgY2VudGVycyA9IDcpCmBgYAoKYGBge3J9CnBsb3Rfbm1kc19jbHVzdGVycyhwYWxlYXJjdGljX2NpdGllcykKYGBgCgpgYGB7cn0KcGFsZWFyY3RpY19iaW9tZXMgPSBzdF9jcm9wKHJlc29sdmVbcmVzb2x2ZSRSRUFMTSA9PSAnUGFsZWFyY3RpYycsYygnUkVBTE0nKV0sIHhtaW4gPSAtMzAsIHltaW4gPSAyMCwgeG1heCA9IDgwLCB5bWF4ID0gNjUpCiAKZ2dwbG90KCkgKyAKICBnZW9tX3NmKGRhdGEgPSBwYWxlYXJjdGljX2Jpb21lcywgYWVzKGdlb21ldHJ5ID0gZ2VvbWV0cnkpKSArIAogIGdlb21fc2YoZGF0YSA9IHBhbGVhcmN0aWNfY2l0aWVzLCBhZXMoZ2VvbWV0cnkgPSBnZW9tZXRyeSwgY29sb3IgPSBjbHVzdGVyKSkKZ2dzYXZlKGZpbGVuYW1lKFJFR0lPTl9ERUVQX0RJVkVfRklHVVJFU19PVVRQVVQsICdwYWxlYXJjdGljX2NsdXN0ZXJzLmpwZycpKQpgYGAKCmBgYHtyfQp0ZXN0X3ZhbHVlKCdQYWxlYXJjdGljIE1OVEQnLCBwYWxlYXJjdGljX2NpdGllcyRtbnRkX25vcm1hbGlzZWQpCnRlc3RfdmFsdWUoJ1BhbGVhcmN0aWMgYmVhayBnYXBlIEZEaXYnLCBwYWxlYXJjdGljX2NpdGllcyRnYXBlX3dpZHRoX2ZkaXZfbm9ybWFsaXNlZCkKdGVzdF92YWx1ZSgnUGFsZWFyY3RpYyBIV0kgRkRpdicsIHBhbGVhcmN0aWNfY2l0aWVzJGhhbmR3aW5nX2luZGV4X2ZkaXZfbm9ybWFsaXNlZCkKbnJvdyhwYWxlYXJjdGljX2NpdGllcykKYGBgCgojIyMgUGFsZWFyY3RpYyBDbHVzdGVyIDEKYGBge3J9CnBhbGVhcmN0aWNfY2x1c3RlcjEgPSBwYWxlYXJjdGljX2NpdGllcyAlPiUgZmlsdGVyKGNsdXN0ZXIgPT0gMSkKCnBsb3RfY2l0eV9jbHVzdGVyKHBhbGVhcmN0aWNfY2x1c3RlcjEsICdQYWxlYXJjdGljIGNsdXN0ZXIgMScpCmdnc2F2ZShmaWxlbmFtZShSRUdJT05fREVFUF9ESVZFX0ZJR1VSRVNfT1VUUFVULCAncGFsZWFyY3RpY19jbHVzdGVyMS5qcGcnKSwgd2lkdGggPSBnZXRfaW1hZ2Vfd2lkdGgocGFsZWFyY3RpY19jbHVzdGVyMSksIGhlaWdodCA9IGdldF9pbWFnZV9oZWlnaHQocGFsZWFyY3RpY19jbHVzdGVyMSksIHVuaXRzID0gIm1tIikKYGBgCgojIyMgUGFsZWFyY3RpYyBDbHVzdGVyIDIKYGBge3J9CnBhbGVhcmN0aWNfY2x1c3RlcjIgPSBwYWxlYXJjdGljX2NpdGllcyAlPiUgZmlsdGVyKGNsdXN0ZXIgPT0gMikKcGxvdF9jaXR5X2NsdXN0ZXIocGFsZWFyY3RpY19jbHVzdGVyMiwgJ1BhbGVhcmN0aWMgY2x1c3RlciAyJykKZ2dzYXZlKGZpbGVuYW1lKFJFR0lPTl9ERUVQX0RJVkVfRklHVVJFU19PVVRQVVQsICdwYWxlYXJjdGljX2NsdXN0ZXIyLmpwZycpLCB3aWR0aCA9IGdldF9pbWFnZV93aWR0aChwYWxlYXJjdGljX2NsdXN0ZXIyKSwgaGVpZ2h0ID0gZ2V0X2ltYWdlX2hlaWdodChwYWxlYXJjdGljX2NsdXN0ZXIyKSwgdW5pdHMgPSAibW0iKQpgYGAKCiMjIyBQYWxlYXJjdGljIENsdXN0ZXIgMwpgYGB7cn0KcGFsZWFyY3RpY19jbHVzdGVyMyA9IHBhbGVhcmN0aWNfY2l0aWVzICU+JSBmaWx0ZXIoY2x1c3RlciA9PSAzKQpwbG90X2NpdHlfY2x1c3RlcihwYWxlYXJjdGljX2NsdXN0ZXIzLCAnUGFsZWFyY3RpYyBjbHVzdGVyIDMnKQpnZ3NhdmUoZmlsZW5hbWUoUkVHSU9OX0RFRVBfRElWRV9GSUdVUkVTX09VVFBVVCwgJ3BhbGVhcmN0aWNfY2x1c3RlcjMuanBnJyksIHdpZHRoID0gZ2V0X2ltYWdlX3dpZHRoKHBhbGVhcmN0aWNfY2x1c3RlcjMpLCBoZWlnaHQgPSBnZXRfaW1hZ2VfaGVpZ2h0KHBhbGVhcmN0aWNfY2x1c3RlcjMpLCB1bml0cyA9ICJtbSIpCmBgYAoKIyMjIFBhbGVhcmN0aWMgQ2x1c3RlciA0CmBgYHtyfQpwYWxlYXJjdGljX2NsdXN0ZXI0ID0gcGFsZWFyY3RpY19jaXRpZXMgJT4lIGZpbHRlcihjbHVzdGVyID09IDQpCnBsb3RfY2l0eV9jbHVzdGVyKHBhbGVhcmN0aWNfY2x1c3RlcjQsICdQYWxlYXJjdGljIGNsdXN0ZXIgNCcpCmdnc2F2ZShmaWxlbmFtZShSRUdJT05fREVFUF9ESVZFX0ZJR1VSRVNfT1VUUFVULCAncGFsZWFyY3RpY19jbHVzdGVyNC5qcGcnKSwgd2lkdGggPSBnZXRfaW1hZ2Vfd2lkdGgocGFsZWFyY3RpY19jbHVzdGVyNCksIGhlaWdodCA9IGdldF9pbWFnZV9oZWlnaHQocGFsZWFyY3RpY19jbHVzdGVyNCksIHVuaXRzID0gIm1tIikKYGBgCgojIyMgUGFsZWFyY3RpYyBDbHVzdGVyIDUKYGBge3J9CnBhbGVhcmN0aWNfY2x1c3RlcjUgPSBwYWxlYXJjdGljX2NpdGllcyAlPiUgZmlsdGVyKGNsdXN0ZXIgPT0gNSkKbnJvdyhwYWxlYXJjdGljX2NsdXN0ZXI1KQpwbG90X2NpdHlfY2x1c3RlcihwYWxlYXJjdGljX2NsdXN0ZXI1WzE6MjUsXSwgJ1BhbGVhcmN0aWMgY2x1c3RlciA1YScpCmdnc2F2ZShmaWxlbmFtZShSRUdJT05fREVFUF9ESVZFX0ZJR1VSRVNfT1VUUFVULCAncGFsZWFyY3RpY19jbHVzdGVyNS5qcGcnKSwgd2lkdGggPSBnZXRfaW1hZ2Vfd2lkdGgocGFsZWFyY3RpY19jbHVzdGVyNSksIGhlaWdodCA9IGdldF9pbWFnZV9oZWlnaHQocGFsZWFyY3RpY19jbHVzdGVyNSksIHVuaXRzID0gIm1tIikKYGBgCgpgYGB7cn0KcGxvdF9jaXR5X2NsdXN0ZXIocGFsZWFyY3RpY19jbHVzdGVyNVsyNTo1MCxdLCAnUGFsZWFyY3RpYyBjbHVzdGVyIDViJykKZ2dzYXZlKGZpbGVuYW1lKFJFR0lPTl9ERUVQX0RJVkVfRklHVVJFU19PVVRQVVQsICdwYWxlYXJjdGljX2NsdXN0ZXI1Yi5qcGcnKSwgd2lkdGggPSBnZXRfaW1hZ2Vfd2lkdGgocGFsZWFyY3RpY19jbHVzdGVyNSksIGhlaWdodCA9IGdldF9pbWFnZV9oZWlnaHQocGFsZWFyY3RpY19jbHVzdGVyNSksIHVuaXRzID0gIm1tIikKYGBgCgojIyMgUGFsZWFyY3RpYyBDbHVzdGVyIDYKYGBge3J9CnBhbGVhcmN0aWNfY2x1c3RlcjYgPSBwYWxlYXJjdGljX2NpdGllcyAlPiUgZmlsdGVyKGNsdXN0ZXIgPT0gNikKcGxvdF9jaXR5X2NsdXN0ZXIocGFsZWFyY3RpY19jbHVzdGVyNiwgJ1BhbGVhcmN0aWMgY2x1c3RlciA2JykKZ2dzYXZlKGZpbGVuYW1lKFJFR0lPTl9ERUVQX0RJVkVfRklHVVJFU19PVVRQVVQsICdwYWxlYXJjdGljX2NsdXN0ZXI2LmpwZycpLCB3aWR0aCA9IGdldF9pbWFnZV93aWR0aChwYWxlYXJjdGljX2NsdXN0ZXI2KSwgaGVpZ2h0ID0gZ2V0X2ltYWdlX2hlaWdodChwYWxlYXJjdGljX2NsdXN0ZXI2KSwgdW5pdHMgPSAibW0iKQpgYGAKCiMjIyBQYWxlYXJjdGljIENsdXN0ZXIgNwpgYGB7cn0KcGFsZWFyY3RpY19jbHVzdGVyNyA9IHBhbGVhcmN0aWNfY2l0aWVzICU+JSBmaWx0ZXIoY2x1c3RlciA9PSA3KQpwbG90X2NpdHlfY2x1c3RlcihwYWxlYXJjdGljX2NsdXN0ZXI3LCAnUGFsZWFyY3RpYyBjbHVzdGVyIDcnKQpnZ3NhdmUoZmlsZW5hbWUoUkVHSU9OX0RFRVBfRElWRV9GSUdVUkVTX09VVFBVVCwgJ3BhbGVhcmN0aWNfY2x1c3RlcjcuanBnJyksIHdpZHRoID0gZ2V0X2ltYWdlX3dpZHRoKHBhbGVhcmN0aWNfY2x1c3RlcjcpLCBoZWlnaHQgPSBnZXRfaW1hZ2VfaGVpZ2h0KHBhbGVhcmN0aWNfY2x1c3RlcjcpLCB1bml0cyA9ICJtbSIpCmBgYAoKIyMgQWZyb3Ryb3BpYwpgYGB7cn0KYWZyb3Ryb3BpY19jaXRpZXNfY29tbXVuaXR5X2RhdGEgPSBjb21tdW5pdHlfZGF0YV9tZXRyaWNzICU+JSBmaWx0ZXIoY29yZV9yZWFsbSA9PSAnQWZyb3Ryb3BpYycpCmFmcm90cm9waWNfY2l0aWVzX2NvbW11bml0eV9kYXRhICU+JSBkcGx5cjo6c2VsZWN0KGNpdHlfbmFtZSkgJT4lIGRpc3RpbmN0KCkgJT4lIGFzLmxpc3QoKQpgYGAKCmBgYHtyfQphZnJvdHJvcGljX2NpdGllc19ubWRzID0gY29tbXVuaXR5X25tZHMoY29tbXVuaXRpZXMgJT4lIGZpbHRlcihjaXR5X2lkICVpbiUgYWZyb3Ryb3BpY19jaXRpZXNfY29tbXVuaXR5X2RhdGEkY2l0eV9pZCkpIAphZnJvdHJvcGljX2NpdGllc19ubWRzCmBgYAoKYGBge3J9CnNjcmVlX3Bsb3QoYWZyb3Ryb3BpY19jaXRpZXNfbm1kcykKYGBgCgpgYGB7cn0KYWZyb3Ryb3BpY19jaXRpZXMgPSBjbHVzdGVyX2NpdGllcyhjaXR5X25tZHMgPSBhZnJvdHJvcGljX2NpdGllc19ubWRzLCBjaXRpZXNfY29tbXVuaXR5X2RhdGEgPSBhZnJvdHJvcGljX2NpdGllc19jb21tdW5pdHlfZGF0YSwgY2VudGVycyA9IDIpCmBgYAoKYGBge3J9CnBsb3Rfbm1kc19jbHVzdGVycyhhZnJvdHJvcGljX2NpdGllcykKYGBgCgpgYGB7cn0KYWZyb3Ryb3BpY19iaW9tZXMgPSByZXNvbHZlW3Jlc29sdmUkUkVBTE0gPT0gJ0Fmcm90cm9waWMnLGMoJ1JFQUxNJyldCiAKZ2dwbG90KCkgKyAKICBnZW9tX3NmKGRhdGEgPSBhZnJvdHJvcGljX2Jpb21lcywgYWVzKGdlb21ldHJ5ID0gZ2VvbWV0cnkpKSArIAogIGdlb21fc2YoZGF0YSA9IGFmcm90cm9waWNfY2l0aWVzLCBhZXMoZ2VvbWV0cnkgPSBnZW9tZXRyeSwgY29sb3IgPSBjbHVzdGVyKSkKZ2dzYXZlKGZpbGVuYW1lKFJFR0lPTl9ERUVQX0RJVkVfRklHVVJFU19PVVRQVVQsICdhZnJvdHJvcGljX2NsdXN0ZXJzLmpwZycpKQpgYGAKCmBgYHtyfQp0ZXN0X3ZhbHVlKCdBZnJvdHJvcGljIE1OVEQnLCBhZnJvdHJvcGljX2NpdGllcyRtbnRkX25vcm1hbGlzZWQpCnRlc3RfdmFsdWUoJ0Fmcm90cm9waWMgYmVhayBnYXBlIEZEaXYnLCBhZnJvdHJvcGljX2NpdGllcyRnYXBlX3dpZHRoX2ZkaXZfbm9ybWFsaXNlZCkKdGVzdF92YWx1ZSgnQWZyb3Ryb3BpYyBIV0kgRkRpdicsIGFmcm90cm9waWNfY2l0aWVzJGhhbmR3aW5nX2luZGV4X2ZkaXZfbm9ybWFsaXNlZCkKbnJvdyhhZnJvdHJvcGljX2NpdGllcykKYGBgCgojIyMgQWZyb3Ryb3BpYyBDbHVzdGVyIDEKYGBge3J9CmFmcm90cm9waWNfY2x1c3RlcjEgPSBhZnJvdHJvcGljX2NpdGllcyAlPiUgZmlsdGVyKGNsdXN0ZXIgPT0gMSkKcGxvdF9jaXR5X2NsdXN0ZXIoYWZyb3Ryb3BpY19jbHVzdGVyMSwgJ0Fmcm90cm9waWMgY2x1c3RlciAxJykKZ2dzYXZlKGZpbGVuYW1lKFJFR0lPTl9ERUVQX0RJVkVfRklHVVJFU19PVVRQVVQsICdhZnJvdHJvcGljX2NsdXN0ZXIxLmpwZycpLCB3aWR0aCA9IGdldF9pbWFnZV93aWR0aChhZnJvdHJvcGljX2NsdXN0ZXIxKSwgaGVpZ2h0ID0gZ2V0X2ltYWdlX2hlaWdodChhZnJvdHJvcGljX2NsdXN0ZXIxKSwgdW5pdHMgPSAibW0iKQpgYGAKCiMjIyBBZnJvdHJvcGljIENsdXN0ZXIgMgpgYGB7cn0KYWZyb3Ryb3BpY19jbHVzdGVyMiA9IGFmcm90cm9waWNfY2l0aWVzICU+JSBmaWx0ZXIoY2x1c3RlciA9PSAyKQpwbG90X2NpdHlfY2x1c3RlcihhZnJvdHJvcGljX2NsdXN0ZXIyLCAnQWZyb3Ryb3BpYyBjbHVzdGVyIDInKQpnZ3NhdmUoZmlsZW5hbWUoUkVHSU9OX0RFRVBfRElWRV9GSUdVUkVTX09VVFBVVCwgJ2Fmcm90cm9waWNfY2x1c3RlcjIuanBnJyksIHdpZHRoID0gZ2V0X2ltYWdlX3dpZHRoKGFmcm90cm9waWNfY2x1c3RlcjIpLCBoZWlnaHQgPSBnZXRfaW1hZ2VfaGVpZ2h0KGFmcm90cm9waWNfY2x1c3RlcjIpLCB1bml0cyA9ICJtbSIpCmBgYAoKIyMgSW5kb21hbGF5YW4KYGBge3J9CmluZG9tYWxheWFuX2NpdGllc19jb21tdW5pdHlfZGF0YSA9IGNvbW11bml0eV9kYXRhX21ldHJpY3MgJT4lIGZpbHRlcihjb3JlX3JlYWxtID09ICdJbmRvbWFsYXlhbicpCmluZG9tYWxheWFuX2NpdGllc19jb21tdW5pdHlfZGF0YSAlPiUgZHBseXI6OnNlbGVjdChjaXR5X25hbWUpICU+JSBkaXN0aW5jdCgpICU+JSBhcy5saXN0KCkKYGBgCgpgYGB7cn0KaW5kb21hbGF5YW5fY2l0aWVzX25tZHMgPSBjb21tdW5pdHlfbm1kcyhjb21tdW5pdGllcyAlPiUgZmlsdGVyKGNpdHlfaWQgJWluJSBpbmRvbWFsYXlhbl9jaXRpZXNfY29tbXVuaXR5X2RhdGEkY2l0eV9pZCkpIAppbmRvbWFsYXlhbl9jaXRpZXNfbm1kcwpgYGAKCmBgYHtyfQpzY3JlZV9wbG90KGluZG9tYWxheWFuX2NpdGllc19ubWRzKQpgYGAKCmBgYHtyfQppbmRvbWFsYXlhbl9jaXRpZXMgPSBjbHVzdGVyX2NpdGllcyhjaXR5X25tZHMgPSBpbmRvbWFsYXlhbl9jaXRpZXNfbm1kcywgY2l0aWVzX2NvbW11bml0eV9kYXRhID0gaW5kb21hbGF5YW5fY2l0aWVzX2NvbW11bml0eV9kYXRhLCBjZW50ZXJzID0gNSkKYGBgCgpgYGB7cn0KcGxvdF9ubWRzX2NsdXN0ZXJzKGluZG9tYWxheWFuX2NpdGllcykKYGBgCgpgYGB7cn0KaW5kb21hbGF5YW5fYmlvbWVzID0gcmVzb2x2ZVtyZXNvbHZlJFJFQUxNID09ICdJbmRvbWFsYXlhbicsYygnUkVBTE0nKV0KIApnZ3Bsb3QoKSArIAogIGdlb21fc2YoZGF0YSA9IGluZG9tYWxheWFuX2Jpb21lcywgYWVzKGdlb21ldHJ5ID0gZ2VvbWV0cnkpKSArIAogIGdlb21fc2YoZGF0YSA9IGluZG9tYWxheWFuX2NpdGllcywgYWVzKGdlb21ldHJ5ID0gZ2VvbWV0cnksIGNvbG9yID0gY2x1c3RlcikpCmdnc2F2ZShmaWxlbmFtZShSRUdJT05fREVFUF9ESVZFX0ZJR1VSRVNfT1VUUFVULCAnaW5kb21hbGF5YW5fY2x1c3RlcnMuanBnJykpCmBgYAoKYGBge3J9CnRlc3RfdmFsdWUoJ0luZG9tYWxheWFuIE1OVEQnLCBpbmRvbWFsYXlhbl9jaXRpZXMkbW50ZF9ub3JtYWxpc2VkKQp0ZXN0X3ZhbHVlKCdJbmRvbWFsYXlhbiBiZWFrIGdhcGUgRkRpdicsIGluZG9tYWxheWFuX2NpdGllcyRnYXBlX3dpZHRoX2ZkaXZfbm9ybWFsaXNlZCkKdGVzdF92YWx1ZSgnSW5kb21hbGF5YW4gSFdJIEZEaXYnLCBpbmRvbWFsYXlhbl9jaXRpZXMkaGFuZHdpbmdfaW5kZXhfZmRpdl9ub3JtYWxpc2VkKQpucm93KGluZG9tYWxheWFuX2NpdGllcykKYGBgCgojIyMgSW5kb21hbGF5YW4gQ2x1c3RlciAxCmBgYHtyfQppbmRvbWFsYXlhbl9jbHVzdGVyMSA9IGluZG9tYWxheWFuX2NpdGllcyAlPiUgZmlsdGVyKGNsdXN0ZXIgPT0gMSkKcGxvdF9jaXR5X2NsdXN0ZXIoaW5kb21hbGF5YW5fY2x1c3RlcjEsICdJbmRvbWFsYXlhbiBjbHVzdGVyIDEnKQpnZ3NhdmUoZmlsZW5hbWUoUkVHSU9OX0RFRVBfRElWRV9GSUdVUkVTX09VVFBVVCwgJ2luZG9tYWxheWFuX2NsdXN0ZXIxLmpwZycpLCB3aWR0aCA9IGdldF9pbWFnZV93aWR0aChpbmRvbWFsYXlhbl9jbHVzdGVyMSksIGhlaWdodCA9IGdldF9pbWFnZV9oZWlnaHQoaW5kb21hbGF5YW5fY2x1c3RlcjEpLCB1bml0cyA9ICJtbSIpCmBgYAoKIyMjIEluZG9tYWxheWFuIENsdXN0ZXIgMgpgYGB7cn0KaW5kb21hbGF5YW5fY2x1c3RlcjIgPSBpbmRvbWFsYXlhbl9jaXRpZXMgJT4lIGZpbHRlcihjbHVzdGVyID09IDIpCnBsb3RfY2l0eV9jbHVzdGVyKGluZG9tYWxheWFuX2NsdXN0ZXIyLCAnSW5kb21hbGF5YW4gY2x1c3RlciAyJykKZ2dzYXZlKGZpbGVuYW1lKFJFR0lPTl9ERUVQX0RJVkVfRklHVVJFU19PVVRQVVQsICdpbmRvbWFsYXlhbl9jbHVzdGVyMi5qcGcnKSwgd2lkdGggPSBnZXRfaW1hZ2Vfd2lkdGgoaW5kb21hbGF5YW5fY2x1c3RlcjIpLCBoZWlnaHQgPSBnZXRfaW1hZ2VfaGVpZ2h0KGluZG9tYWxheWFuX2NsdXN0ZXIyKSwgdW5pdHMgPSAibW0iKQpgYGAKCiMjIyBJbmRvbWFsYXlhbiBDbHVzdGVyIDMKYGBge3J9CmluZG9tYWxheWFuX2NsdXN0ZXIzID0gaW5kb21hbGF5YW5fY2l0aWVzICU+JSBmaWx0ZXIoY2x1c3RlciA9PSAzKQpwbG90X2NpdHlfY2x1c3RlcihpbmRvbWFsYXlhbl9jbHVzdGVyMywgJ0luZG9tYWxheWFuIGNsdXN0ZXIgMycpCmdnc2F2ZShmaWxlbmFtZShSRUdJT05fREVFUF9ESVZFX0ZJR1VSRVNfT1VUUFVULCAnaW5kb21hbGF5YW5fY2x1c3RlcjMuanBnJyksIHdpZHRoID0gZ2V0X2ltYWdlX3dpZHRoKGluZG9tYWxheWFuX2NsdXN0ZXIzKSwgaGVpZ2h0ID0gZ2V0X2ltYWdlX2hlaWdodChpbmRvbWFsYXlhbl9jbHVzdGVyMyksIHVuaXRzID0gIm1tIikKYGBgCgojIyMgSW5kb21hbGF5YW4gQ2x1c3RlciA0CmBgYHtyfQppbmRvbWFsYXlhbl9jbHVzdGVyNCA9IGluZG9tYWxheWFuX2NpdGllcyAlPiUgZmlsdGVyKGNsdXN0ZXIgPT0gNCkKcGxvdF9jaXR5X2NsdXN0ZXIoaW5kb21hbGF5YW5fY2x1c3RlcjQsICdJbmRvbWFsYXlhbiBjbHVzdGVyIDQnKQpnZ3NhdmUoZmlsZW5hbWUoUkVHSU9OX0RFRVBfRElWRV9GSUdVUkVTX09VVFBVVCwgJ2luZG9tYWxheWFuX2NsdXN0ZXI0LmpwZycpLCB3aWR0aCA9IGdldF9pbWFnZV93aWR0aChpbmRvbWFsYXlhbl9jbHVzdGVyNCksIGhlaWdodCA9IGdldF9pbWFnZV9oZWlnaHQoaW5kb21hbGF5YW5fY2x1c3RlcjQpLCB1bml0cyA9ICJtbSIpCmBgYAoKIyMjIEluZG9tYWxheWFuIENsdXN0ZXIgNQpgYGB7cn0KaW5kb21hbGF5YW5fY2x1c3RlcjUgPSBpbmRvbWFsYXlhbl9jaXRpZXMgJT4lIGZpbHRlcihjbHVzdGVyID09IDUpCnBsb3RfY2l0eV9jbHVzdGVyKGluZG9tYWxheWFuX2NsdXN0ZXI1LCAnSW5kb21hbGF5YW4gY2x1c3RlciA1JykKZ2dzYXZlKGZpbGVuYW1lKFJFR0lPTl9ERUVQX0RJVkVfRklHVVJFU19PVVRQVVQsICdpbmRvbWFsYXlhbl9jbHVzdGVyNS5qcGcnKSwgd2lkdGggPSBnZXRfaW1hZ2Vfd2lkdGgoaW5kb21hbGF5YW5fY2x1c3RlcjUpLCBoZWlnaHQgPSBnZXRfaW1hZ2VfaGVpZ2h0KGluZG9tYWxheWFuX2NsdXN0ZXI1KSwgdW5pdHMgPSAibW0iKQpgYGAKCiMjIEF1c3RyYWxhc2lhCmBgYHtyfQphdXN0cmFsYXNpYV9jaXRpZXNfY29tbXVuaXR5X2RhdGEgPSBjb21tdW5pdHlfZGF0YV9tZXRyaWNzICU+JSBmaWx0ZXIoY29yZV9yZWFsbSA9PSAnQXVzdHJhbGFzaWEnKQphdXN0cmFsYXNpYV9jaXRpZXNfY29tbXVuaXR5X2RhdGEgJT4lIGRwbHlyOjpzZWxlY3QoY2l0eV9uYW1lKSAlPiUgZGlzdGluY3QoKSAlPiUgYXMubGlzdCgpCmBgYAoKYGBge3J9CmF1c3RyYWxhc2lhX2NpdGllc19ubWRzID0gY29tbXVuaXR5X25tZHMoY29tbXVuaXRpZXMgJT4lIGZpbHRlcihjaXR5X2lkICVpbiUgYXVzdHJhbGFzaWFfY2l0aWVzX2NvbW11bml0eV9kYXRhJGNpdHlfaWQpKSAKYXVzdHJhbGFzaWFfY2l0aWVzX25tZHMKYGBgCgpgYGB7cn0Kc2NyZWVfcGxvdChhdXN0cmFsYXNpYV9jaXRpZXNfbm1kcykKYGBgCgpgYGB7cn0KYXVzdHJhbGFzaWFfY2l0aWVzID0gY2x1c3Rlcl9jaXRpZXMoY2l0eV9ubWRzID0gYXVzdHJhbGFzaWFfY2l0aWVzX25tZHMsIGNpdGllc19jb21tdW5pdHlfZGF0YSA9IGF1c3RyYWxhc2lhX2NpdGllc19jb21tdW5pdHlfZGF0YSwgY2VudGVycyA9IDMpCmBgYAoKYGBge3J9CnBsb3Rfbm1kc19jbHVzdGVycyhhdXN0cmFsYXNpYV9jaXRpZXMpCmBgYAoKYGBge3J9CmF1c3RyYWxhc2lhX2Jpb21lcyA9IHN0X2Nyb3AocmVzb2x2ZVtyZXNvbHZlJFJFQUxNID09ICdBdXN0cmFsYXNpYScsYygnUkVBTE0nKV0sIHhtaW4gPSA4MCwgeW1pbiA9IC03MCwgeG1heCA9IDE4MCwgeW1heCA9IDIwKQogCmdncGxvdCgpICsgCiAgZ2VvbV9zZihkYXRhID0gYXVzdHJhbGFzaWFfYmlvbWVzLCBhZXMoZ2VvbWV0cnkgPSBnZW9tZXRyeSkpICsgCiAgZ2VvbV9zZihkYXRhID0gYXVzdHJhbGFzaWFfY2l0aWVzLCBhZXMoZ2VvbWV0cnkgPSBnZW9tZXRyeSwgY29sb3IgPSBjbHVzdGVyKSkKZ2dzYXZlKGZpbGVuYW1lKFJFR0lPTl9ERUVQX0RJVkVfRklHVVJFU19PVVRQVVQsICdhdXN0cmFsYXNpYV9jbHVzdGVycy5qcGcnKSkKYGBgCgpgYGB7cn0KdGVzdF92YWx1ZSgnQXVzdHJhbGFzaWEgTU5URCcsIGF1c3RyYWxhc2lhX2NpdGllcyRtbnRkX25vcm1hbGlzZWQpCnRlc3RfdmFsdWUoJ0F1c3RyYWxhc2lhIGJlYWsgZ2FwZSBGRGl2JywgYXVzdHJhbGFzaWFfY2l0aWVzJGdhcGVfd2lkdGhfZmRpdl9ub3JtYWxpc2VkKQp0ZXN0X3ZhbHVlKCdBdXN0cmFsYXNpYSBIV0kgRkRpdicsIGF1c3RyYWxhc2lhX2NpdGllcyRoYW5kd2luZ19pbmRleF9mZGl2X25vcm1hbGlzZWQpCm5yb3coYXVzdHJhbGFzaWFfY2l0aWVzKQpgYGAKIyMjIEF1c3RyYWxhc2lhIENsdXN0ZXIgMQpgYGB7cn0KYXVzdHJhbGFzaWFfY2x1c3RlcjEgPSBhdXN0cmFsYXNpYV9jaXRpZXMgJT4lIGZpbHRlcihjbHVzdGVyID09IDEpCnBsb3RfY2l0eV9jbHVzdGVyKGF1c3RyYWxhc2lhX2NsdXN0ZXIxLCAnQXVzdHJhbGFzaWEgY2x1c3RlciAxJykKZ2dzYXZlKGZpbGVuYW1lKFJFR0lPTl9ERUVQX0RJVkVfRklHVVJFU19PVVRQVVQsICdhdXN0cmFsYXNpYV9jbHVzdGVyMS5qcGcnKSwgd2lkdGggPSBnZXRfaW1hZ2Vfd2lkdGgoYXVzdHJhbGFzaWFfY2x1c3RlcjEpLCBoZWlnaHQgPSBnZXRfaW1hZ2VfaGVpZ2h0KGF1c3RyYWxhc2lhX2NsdXN0ZXIxKSwgdW5pdHMgPSAibW0iKQpgYGAKCmBgYHtyfQphdXN0cmFsYXNpYV9jbHVzdGVyMiA9IGF1c3RyYWxhc2lhX2NpdGllcyAlPiUgZmlsdGVyKGNsdXN0ZXIgPT0gMikKcGxvdF9jaXR5X2NsdXN0ZXIoYXVzdHJhbGFzaWFfY2x1c3RlcjIsICdBdXN0cmFsYXNpYSBjbHVzdGVyIDInKQpnZ3NhdmUoZmlsZW5hbWUoUkVHSU9OX0RFRVBfRElWRV9GSUdVUkVTX09VVFBVVCwgJ2F1c3RyYWxhc2lhX2NsdXN0ZXIyLmpwZycpLCB3aWR0aCA9IGdldF9pbWFnZV93aWR0aChhdXN0cmFsYXNpYV9jbHVzdGVyMiksIGhlaWdodCA9IGdldF9pbWFnZV9oZWlnaHQoYXVzdHJhbGFzaWFfY2x1c3RlcjIpLCB1bml0cyA9ICJtbSIpCmBgYAoKYGBge3J9CmF1c3RyYWxhc2lhX2NsdXN0ZXIzID0gYXVzdHJhbGFzaWFfY2l0aWVzICU+JSBmaWx0ZXIoY2x1c3RlciA9PSAzKQpwbG90X2NpdHlfY2x1c3RlcihhdXN0cmFsYXNpYV9jbHVzdGVyMywgJ0F1c3RyYWxhc2lhIGNsdXN0ZXIgMycpCmdnc2F2ZShmaWxlbmFtZShSRUdJT05fREVFUF9ESVZFX0ZJR1VSRVNfT1VUUFVULCAnYXVzdHJhbGFzaWFfY2x1c3RlcjMuanBnJyksIHdpZHRoID0gZ2V0X2ltYWdlX3dpZHRoKGF1c3RyYWxhc2lhX2NsdXN0ZXIzKSwgaGVpZ2h0ID0gZ2V0X2ltYWdlX2hlaWdodChhdXN0cmFsYXNpYV9jbHVzdGVyMyksIHVuaXRzID0gIm1tIikKYGBg